MiniTensor Version of the Day
Loading...
Searching...
No Matches
MiniTensor_Factorizations.h
Go to the documentation of this file.
1// @HEADER
2// *****************************************************************************
3// MiniTensor Package
4//
5// Copyright 2016 NTESS and the MiniTensor contributors.
6// SPDX-License-Identifier: BSD-3-Clause
7// *****************************************************************************
8// @HEADER
9
10#if !defined(MiniTensor_Factorizations_h)
11#define MiniTensor_Factorizations_h
12
13// Matrix factorizations: eigen, SVD, polar, Cholesky, Givens.
14#include "MiniTensor_Inverse.h"
15#include "MiniTensor_Norms.h"
16
17namespace minitensor {
18
21
30template<typename T, Index N>
32void
33givens_left(T const & c, T const & s, Index i, Index k, Tensor<T, N> & A);
34
43template<typename T, Index N>
45void
46givens_right(T const & c, T const & s, Index i, Index k, Tensor<T, N> & A);
47
55template <typename T, Index N>
56std::pair<Vector<T, N>, Tensor<T, N>> sort_permutation(Vector<T, N> const &u);
57
62template <typename T, Index N>
63std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>> svd(Tensor<T, N> const &A);
64
74template<typename T, Index N>
78
84template <typename T, Index N>
85std::pair<Tensor<T, N>, Tensor<T, N>> polar_left(Tensor<T, N> const &A);
86
92template <typename T, Index N>
93std::pair<Tensor<T, N>, Tensor<T, N>> polar_right(Tensor<T, N> const &A);
94
100template <typename T, Index N>
101std::pair<Tensor<T, N>, Tensor<T, N>> polar_left_eig(Tensor<T, N> const &A);
102
108template <typename T, Index N>
109std::pair<Tensor<T, N>, Tensor<T, N>> polar_right_eig(Tensor<T, N> const &A);
110
116template <typename T, Index N>
117std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>>
119
120template <typename T, Index N>
121std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>>
123
129template <typename T, Index N>
130std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>>
132
140template <typename T>
141std::pair<T, T> schur_sym(const T f, const T g, const T h);
142
147template <typename T> std::pair<T, T> givens(T const &a, T const &b);
148
153template <typename T, Index N>
154std::pair<Tensor<T, N>, Tensor<T, N>> eig_sym(Tensor<T, N> const &A);
155
160template <typename T, Index N>
161std::pair<Tensor<T, N>, Tensor<T, N>> eig_spd(Tensor<T, N> const &A);
162
169template <typename T, Index N>
170std::pair<Tensor<T, N>, Tensor<T, N>> eig_spd_cos(Tensor<T, N> const &A);
171
179template <typename T, Index N>
180std::pair<Tensor<T, N>, bool> cholesky(Tensor<T, N> const &A);
181
185template <typename T, Index N> T cond(Tensor<T, N> const &A);
186
190template <typename T, Index N> T inv_cond(Tensor<T, N> const &A);
191
192//
193// Condition number.
194//
195template <typename T, Index N> T cond(Tensor<T, N> const &A) {
196 Index const
197 dimension = A.get_dimension();
198
199 Tensor<T, N> const S = std::get<1>(svd(A));
200
201 T const
202 k = S(0, 0) / S(dimension - 1, dimension - 1);
203
204 return k;
205}
206
207//
208// Reciprocal condition number.
209//
210template <typename T, Index N> T inv_cond(Tensor<T, N> const &A) {
211 Index const
212 dimension = A.get_dimension();
213
214 Tensor<T, N> const S = std::get<1>(svd(A));
215
216 T const
217 k = S(dimension - 1, dimension - 1) / S(0, 0);
218
219 return k;
220}
221
222//
223// Sort and index in descending order. Useful for ordering singular values
224// and eigenvalues and corresponding vectors in the respective decompositions.
225//
226template <typename T, Index N>
227std::pair<Vector<T, N>, Tensor<T, N>> sort_permutation(Vector<T, N> const &u) {
228
229 Index const
230 dimension = u.get_dimension();
231
232 std::vector <std::pair<T, Index>>
233 s(dimension);
234
235 for (Index i = 0; i < dimension; ++i) {
236 s[i].first = u(i);
237 s[i].second = i;
238 }
239
240 std::sort(s.begin(), s.end(), greater_than<std::pair<T, Index>>);
241
242 Vector<T, N> v(dimension);
243
245 P = zero<T, N>(dimension);
246
247 for (Index i = 0; i < dimension; ++i) {
248 v(i) = s[i].first;
249 P(s[i].second, i) = 1.0;
250 }
251
252 return std::make_pair(v, P);
253}
254
255//
256// Apply Givens-Jacobi rotation on the left in place.
257//
258template<typename T, Index N>
260void
261givens_left(T const & c, T const & s, Index i, Index k, Tensor<T, N> & A)
262{
263 Index const
264 dimension = A.get_dimension();
265
266 for (Index j = 0; j < dimension; ++j) {
267 T const t1 = A(i,j);
268 T const t2 = A(k,j);
269 A(i,j) = c * t1 - s * t2;
270 A(k,j) = s * t1 + c * t2;
271 }
272 return;
273}
274
275//
276// Apply Givens-Jacobi rotation on the right in place.
277//
278template<typename T, Index N>
280void
281givens_right(T const & c, T const & s, Index i, Index k, Tensor<T, N> & A)
282{
283 Index const
284 dimension = A.get_dimension();
285
286 for (Index j = 0; j < dimension; ++j) {
287 T const t1 = A(j,i);
288 T const t2 = A(j,k);
289 A(j,i) = c * t1 - s * t2;
290 A(j,k) = s * t1 + c * t2;
291 }
292 return;
293}
294
295namespace impl {
296
297//
298// Singular value decomposition (SVD) for 2x2
299// bidiagonal matrix. Used for general 2x2 SVD.
300// Adapted from LAPAPCK's DLASV2, Netlib's dlasv2.c
301// and LBNL computational crystallography toolbox
302// \param f, g, h where A = [f, g; 0, h]
303// \return \f$ A = USV^T\f$
304//
305template <typename T, Index N>
306std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>> svd_bidiagonal(T f, T g,
307 T h) {
308 T fa = std::abs(f);
309 T ga = std::abs(g);
310 T ha = std::abs(h);
311
312 T s0 = 0.0;
313 T s1 = 0.0;
314
315 T cu = 1.0;
316 T su = 0.0;
317 T cv = 1.0;
318 T sv = 0.0;
319
320 bool swap_diag = (ha > fa);
321
322 if (swap_diag == true) {
323 std::swap(fa, ha);
324 std::swap(f, h);
325 }
326
327 // diagonal matrix
328 if (ga == 0.0) {
329 s1 = ha;
330 s0 = fa;
331 } else if (ga > fa && fa / ga < machine_epsilon<T>()) {
332 // case of very large ga
333 s0 = ga;
334 s1 = ha > 1.0 ?
335 T(fa / (ga / ha)) :
336 T((fa / ga) * ha);
337 cu = 1.0;
338 su = h / g;
339 cv = f / g;
340 sv = 1.0;
341 } else {
342 // normal case
343 T d = fa - ha;
344 T l = d / fa; // l \in [0,1]
345 T m = g / f; // m \in (-1/macheps, 1/macheps)
346 T t = 2.0 - l; // t \in [1,2]
347 T mm = m * m;
348 T tt = t * t;
349 T s = std::sqrt(tt + mm); // s \in [1,1 + 1/macheps]
350 T r = l != 0.0 ?
351 T(std::sqrt(l * l + mm)) :
352 T(std::abs(m)); // r \in [0,1 + 1/macheps]
353 T a = 0.5 * (s + r); // a \in [1,1 + |m|]
354 s1 = ha / a;
355 s0 = fa * a;
356
357 // Compute singular vectors
358 T tau; // second assignment to T in DLASV2
359 if (mm != 0.0) {
360 tau = (m / (s + t) + m / (r + l)) * (1.0 + a);
361 } else {
362 // note that m is very tiny
363 tau = l == 0.0 ?
364 T(copysign(T(2.0), f) * copysign(T(1.0), g)) :
365 T(g / copysign(d, f) + m / t);
366 }
367 T lv = std::sqrt(tau * tau + 4.0); // second assignment to L in DLASV2
368 cv = 2.0 / lv;
369 sv = tau / lv;
370 cu = (cv + sv * m) / a;
371 su = (h / f) * sv / a;
372 }
373
374 // Fix signs of singular values in accordance to sign of singular vectors
375 s0 = copysign(s0, f);
376 s1 = copysign(s1, h);
377
378 if (swap_diag == true) {
379 std::swap(cu, sv);
380 std::swap(su, cv);
381 }
382
383 Tensor<T, N> U(cu, -su, su, cu);
384 Tensor<T, N> S(s0, 0.0, 0.0, s1);
385 Tensor<T, N> V(cv, -sv, sv, cv);
386
387 return std::make_tuple(U, S, V);
388}
389
390//
391// R^2 singular value decomposition (SVD)
392// \param A tensor
393// \return \f$ A = USV^T\f$
394//
395template <typename T, Index N>
396std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>>
397svd_2x2(Tensor<T, N> const &A) {
398 assert(A.get_dimension() == 2);
399
400 // First compute a givens rotation to eliminate 1,0 entry in tensor
401 T c = 1.0;
402 T s = 0.0;
403 std::tie(c, s) = givens(A(0, 0), A(1, 0));
404
405 Tensor<T, N>
406 R(c, -s, s, c);
407
408 Tensor<T, N>
409 B = R * A;
410
411 // B is bidiagonal. Use specialized algorithm to compute its SVD
412 Tensor<T, N>
413 X(2), S(2), V(2);
414
415 std::tie(X, S, V) = svd_bidiagonal<T, N>(B(0, 0), B(0, 1), B(1, 1));
416
417 // Complete general 2x2 SVD with givens rotation calculated above
418 Tensor<T, N>
419 U = transpose(R) * X;
420
421 return std::make_tuple(U, S, V);
422}
423
424//
425// R^N singular value decomposition (SVD)
426// \param A tensor
427// \return \f$ A = USV^T\f$
428//
429template <typename T, Index N>
430std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>>
431svd_NxN(Tensor<T, N> const &A) {
432 // Scale first
433 T const
434 norm_a = norm(A);
435
436 T const
437 scale = norm_a > 0.0 ? norm_a : T(1.0);
438
439 Tensor<T, N>
440 S = A / scale;
441
442 Index const
443 dimension = A.get_dimension();
444
445 Tensor<T, N>
446 U = identity<T, N>(dimension);
447
448 Tensor<T, N>
449 V = identity<T, N>(dimension);
450
451 T
452 off = norm_off_diagonal(S);
453
454 T const
455 tol = machine_epsilon<T>();
456
457 Index const
458 max_iter = 2048;
459
460 Index
461 num_iter = 0;
462
463 while (off > tol && num_iter < max_iter) {
464
465 // Find largest off-diagonal entry
466 Index
467 p = 0;
468
469 Index
470 q = 0;
471
472 std::tie(p, q) = arg_max_off_diagonal(S);
473
474 if (p > q) {
475 std::swap(p, q);
476 }
477
478 // Obtain left and right Givens rotations by using 2x2 SVD
479 Tensor <T, 2>
480 Spq(S(p,p), S(p,q), S(q,p), S(q,q));
481
482 Tensor <T, 2>
483 L(2), D(2), R(2);
484
485 std::tie(L, D, R) = svd_2x2(Spq);
486
487 T const &
488 cl = L(0,0);
489
490 T const &
491 sl = L(0,1);
492
493 T const &
494 cr = R(0,0);
495
496 T const &
497 sr = (sgn(R(0,1)) == sgn(R(1,0))) ? T(-R(0,1)) : T(R(0,1));
498
499 // Apply both Givens rotations to matrices
500 // that are converging to singular values and singular vectors
501 givens_left(cl, sl, p, q, S);
502 givens_right(cr, sr, p, q, S);
503
504 givens_right(cl, sl, p, q, U);
505 givens_left(cr, sr, p, q, V);
506
507 off = norm_off_diagonal(S);
508 num_iter++;
509 }
510
511 if (num_iter == max_iter) {
512 MT_WARNING("SVD iteration did not converge.");
513 }
514
515 // Fix signs for entries in the diagonal matrix S
516 // that are negative
517 for (Index i = 0; i < dimension; ++i) {
518 if (S(i,i) < 0.0) {
519 S(i,i) = -S(i,i);
520 for (Index j = 0; j < dimension; ++j) {
521 U(j,i) = -U(j,i);
522 }
523 }
524 }
525
526 Vector<T, N> s(dimension);
527 Tensor<T, N> P(dimension);
528
529 std::tie(s, P) = sort_permutation(diag(S));
530 S = scale * diag(s);
531 U = U * P;
532 V = V * P;
533
534 return std::make_tuple(U, diag(diag(S)), transpose(V));
535}
536
537} // namespace impl
538
539//
540// R^N singular value decomposition (SVD)
541// \param A tensor
542// \return \f$ A = USV^T\f$
543//
544template <typename T, Index N>
545std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>>
546svd(Tensor<T, N> const &A) {
547 Index const
548 dimension = A.get_dimension();
549
551 U(dimension), S(dimension), V(dimension);
552
553 switch (dimension) {
554
555 default:
556 std::tie(U, S, V) = impl::svd_NxN(A);
557 break;
558
559 case 2:
560 std::tie(U, S, V) = impl::svd_2x2(A);
561 // svd_2x2 doubles as a building block inside svd_NxN's Jacobi sweep, so
562 // it returns the raw 2x2 factorization without the sign/order
563 // canonicalization that svd_NxN applies to its own result. Apply that
564 // canonicalization here, for the top-level 2D SVD only, so svd() returns
565 // the standard convention (nonnegative singular values in descending
566 // order) regardless of dimension. Folding a negative sign into the
567 // corresponding column of U preserves A = U S V^T.
568 for (Index i = 0; i < dimension; ++i) {
569 if (S(i, i) < 0.0) {
570 S(i, i) = -S(i, i);
571 for (Index j = 0; j < dimension; ++j) {
572 U(j, i) = -U(j, i);
573 }
574 }
575 }
576 // Sort descending. For 2x2 this is a single compare-and-swap of the two
577 // columns of U and V (and the two singular values), avoiding the heap
578 // allocation and matrix multiply that the general sort_permutation
579 // incurs.
580 if (S(0, 0) < S(1, 1)) {
581 std::swap(S(0, 0), S(1, 1));
582 std::swap(U(0, 0), U(0, 1));
583 std::swap(U(1, 0), U(1, 1));
584 std::swap(V(0, 0), V(0, 1));
585 std::swap(V(1, 0), V(1, 1));
586 }
587 break;
588
589 }
590
591 return std::make_tuple(U, S, V);
592}
593
594//
595// Project to O(N) (Orthogonal Group) using a Newton-type algorithm.
596// See Higham's Functions of Matrices p210 [2008]
597// \param A tensor (often a deformation-gradient-like tensor)
598// \return \f$ R = \argmin_Q \|A - Q\|\f$
599// This algorithm projects a given tensor in GL(N) to O(N).
600// The rotation/reflection obtained through this projection is
601// the orthogonal component of the real polar decomposition
602//
603template<typename T, Index N>
605Tensor<T, N>
607{
608 Index const
609 dimension = A.get_dimension();
610
611 bool
612 scale = true;
613
614 T const
615 tol_scale = 0.01;
616
617 T const tol_conv =
618 static_cast<Index>(Kokkos::sqrt(static_cast<double>(dimension))) *
619 machine_epsilon<T>();
620
622 X = A;
623
624 T
625 gamma = 2.0;
626
627 Index const
628 max_iter = 128;
629
630 Index
631 num_iter = 0;
632
633 while (num_iter < max_iter) {
634
636 Y = inverse(X);
637
638 T
639 mu = 1.0;
640
641 if (scale == true) {
642 mu = (norm_1(Y) * norm_infinity(Y)) / (norm_1(X) * norm_infinity(X));
643 mu = std::sqrt(std::sqrt(mu));
644 }
645
647 Z = 0.5 * (mu * X + transpose(Y) / mu);
648
650 D = Z - X;
651
652 T
653 delta = norm(D) / norm(Z);
654
655 if (scale == true && delta < tol_scale) {
656 scale = false;
657 }
658
659 bool
660 end_iter =
661 norm(D) <= std::sqrt(tol_conv) ||
662 (delta > 0.5 * gamma && scale == false);
663
664 X = Z;
665 gamma = delta;
666
667 if (end_iter == true) {
668 break;
669 }
670
671 num_iter++;
672
673 }
674
675 if (num_iter == max_iter) {
676 MT_WARNING("Polar iteration did not converge.");
677 }
678
679 return X;
680}
681
682//
683// R^N Left polar decomposition
684// \param A tensor (often a deformation-gradient-like tensor)
685// \return \f$ VR = A \f$ with \f$ R \in SO(N) \f$ and \f$ V \in SPD(N) \f$
686//
687template <typename T, Index N>
688std::pair<Tensor<T, N>, Tensor<T, N>> polar_left(Tensor<T, N> const &A) {
690 R = polar_rotation(A);
691
693 V = sym(A * transpose(R));
694
695 return std::make_pair(V, R);
696}
697
698//
699// R^N Right polar decomposition
700// \param A tensor (often a deformation-gradient-like tensor)
701// \return \f$ RU = A \f$ with \f$ R \in SO(N) \f$ and \f$ U \in SPD(N) \f$
702//
703template <typename T, Index N>
704std::pair<Tensor<T, N>, Tensor<T, N>> polar_right(Tensor<T, N> const &A) {
706 R = polar_rotation(A);
707
709 U = sym(transpose(R) * A);
710
711 return std::make_pair(R, U);
712}
713
714//
715// R^3 left polar decomposition with eigenvalue decomposition
716// \param F tensor (often a deformation-gradient-like tensor)
717// \return \f$ VR = F \f$ with \f$ R \in SO(3) \f$ and V SPD(3)
718//
719template <typename T, Index N>
720std::pair<Tensor<T, N>, Tensor<T, N>> polar_left_eig(Tensor<T, N> const &F) {
721 assert(F.get_dimension() == 3);
722
723 // set up return tensors
725 R(3);
726
728 V(3);
729
730 // temporary tensor used to compute R
732 Vinv(3);
733
734 // compute spd tensor
736 b = F * transpose(F);
737
738 // get eigenvalues/eigenvectors
740 eVal(3);
741
743 eVec(3);
744 std::tie(eVec, eVal) = eig_spd(b);
745
746 // compute sqrt() and inv(sqrt()) of eigenvalues
748 x = zero<T, N>(3);
749
750 for (Index i = 0; i < 3; ++i) {
751 if (eVal(i, i) < T(0)) {
752 MT_ERROR_EXIT("Non-SPD input: negative eigenvalue.");
753 }
754 }
755
756 x(0,0) = std::sqrt(eVal(0,0));
757 x(1,1) = std::sqrt(eVal(1,1));
758 x(2,2) = std::sqrt(eVal(2,2));
759
761 xi = zero<T, N>(3);
762
763 xi(0,0) = 1.0 / x(0,0);
764 xi(1,1) = 1.0 / x(1,1);
765 xi(2,2) = 1.0 / x(2,2);
766
767 // compute V, Vinv, and R
768 V = eVec * x * transpose(eVec);
769 Vinv = eVec * xi * transpose(eVec);
770 R = Vinv * F;
771 return std::make_pair(V, R);
772}
773
774//
775// R^3 right polar decomposition with eigenvalue decomposition
776// \param F tensor (often a deformation-gradient-like tensor)
777// \return \f$ RU = F \f$ with \f$ R \in SO(3) \f$ and U SPD(3)
778//
779template <typename T, Index N>
780std::pair<Tensor<T, N>, Tensor<T, N>> polar_right_eig(Tensor<T, N> const &F) {
781 Index const
782 dimension = F.get_dimension();
783
784 assert(dimension == 3);
785
787 R(dimension);
788
790 U(dimension);
791
792 // temporary tensor used to compute R
794 Uinv(dimension);
795
796 // compute spd tensor
798 C = transpose(F) * F;
799
800 // get eigenvalues/eigenvectors
802 eVal(dimension);
803
805 eVec(dimension);
806
807 std::tie(eVec, eVal) = eig_spd(C);
808
809 // compute sqrt() and inv(sqrt()) of eigenvalues
811 x = zero<T, N>(dimension);
812
813 for (Index i = 0; i < dimension; ++i) {
814 if (eVal(i, i) < T(0)) {
815 MT_ERROR_EXIT("Non-SPD input: negative eigenvalue.");
816 }
817 }
818
819 x(0,0) = std::sqrt(eVal(0,0));
820 x(1,1) = std::sqrt(eVal(1,1));
821 x(2,2) = std::sqrt(eVal(2,2));
822
824 xi = zero<T, N>(dimension);
825
826 xi(0,0) = 1.0 / x(0,0);
827 xi(1,1) = 1.0 / x(1,1);
828 xi(2,2) = 1.0 / x(2,2);
829
830 // compute U, Uinv, and R
831 U = eVec * x * transpose(eVec);
832 Uinv = eVec * xi * transpose(eVec);
833 R = F * Uinv;
834
835 return std::make_pair(R, U);
836}
837
838//
839// R^N left polar decomposition with matrix logarithm for V
840// \param F tensor (often a deformation-gradient-like tensor)
841// \return \f$ VR = F \f$ with \f$ R \in SO(N) \f$ and V SPD(N), and log V
842//
843template <typename T, Index N>
844std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>>
846 Index const
847 dimension = F.get_dimension();
848
850 X(dimension), S(dimension), Y(dimension);
851
852 std::tie(X, S, Y) = svd(F);
853
855 R = X * transpose(Y);
856
858 V = X * S * transpose(X);
859
861 s = S;
862
863 for (Index i = 0; i < dimension; ++i) {
864 s(i,i) = std::log(s(i,i));
865 }
866
868 v = X * s * transpose(X);
869
870 return std::make_tuple(V, R, v);
871}
872
877template <typename T, Index N>
878std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>>
880 Index const
881 dimension = F.get_dimension();
882
883 Tensor<T, N> const
884 b = dot_t(F, F);
885
887 V(dimension), D(dimension);
888
889 std::tie(V, D) = eig_sym(b);
890
892 DQ(dimension, Filler::ZEROS), DI(dimension, Filler::ZEROS), DL(dimension, Filler::ZEROS);
893
894 for (Index i = 0; i < dimension; ++i) {
895 if (D(i,i) < T(0)) {
896 MT_ERROR_EXIT("Non-SPD input: negative eigenvalue.");
897 }
898 DQ(i,i) = std::sqrt(D(i,i));
899 DI(i,i) = 1.0 / DQ(i,i);
900 DL(i,i) = std::log(DQ(i,i));
901 }
902
903 Tensor<T, N> const
904 R = dot(V, DI) * t_dot(V, F);
905
906 Tensor<T, N> const
907 X = V * dot_t(DQ, V);
908
909 Tensor<T, N> const
910 x = V * dot_t(DL, V);
911
912 return std::make_tuple(X, R, x);
913}
914
915//
916// R^N left polar decomposition with matrix logarithm for V
917// \param F tensor (often a deformation-gradient-like tensor)
918// \return \f$ VR = F \f$ with \f$ R \in SO(N) \f$ and V SPD(N), and log V
919//
920template <typename T, Index N>
921std::tuple<Tensor<T, N>, Tensor<T, N>, Tensor<T, N>>
923 Index const
924 dimension = F.get_dimension();
925
926 // set up return tensors
927 Tensor<T, N> R(dimension), V(dimension), v(dimension), Vinv(dimension);
928
929 // compute spd tensor
930 Tensor<T, N> b = F*transpose(F);
931
932 // get eigenvalues/eigenvectors
933 Tensor<T, N> eVal(dimension);
934 Tensor<T, N> eVec(dimension);
935 std::tie(eVec, eVal) = eig_spd_cos(b);
936
937 // compute sqrt() and inv(sqrt()) of eigenvalues
938 for (Index i = 0; i < 3; ++i) {
939 if (eVal(i,i) < T(0)) {
940 MT_ERROR_EXIT("Non-SPD input: negative eigenvalue.");
941 }
942 }
943 Tensor<T, N> x = zero<T, N>(3);
944 x(0,0) = std::sqrt(eVal(0,0));
945 x(1,1) = std::sqrt(eVal(1,1));
946 x(2,2) = std::sqrt(eVal(2,2));
947 Tensor<T, N> xi = zero<T, N>(3);
948 xi(0,0) = 1.0/x(0,0);
949 xi(1,1) = 1.0/x(1,1);
950 xi(2,2) = 1.0/x(2,2);
951 Tensor<T, N> lnx = zero<T, N>(3);
952 lnx(0,0) = std::log(x(0,0));
953 lnx(1,1) = std::log(x(1,1));
954 lnx(2,2) = std::log(x(2,2));
955 // compute V, Vinv, log(V)=v, and R
956 V = eVec*x*transpose(eVec);
957 Vinv = eVec*xi*transpose(eVec);
958 v = eVec*lnx*transpose(eVec);
959 R = Vinv*F;
960
961 return std::make_tuple(V, R, v);
962}
963
964//
965// Symmetric Schur algorithm for R^2.
966// \param \f$ A = [f, g; g, h] \in S(2) \f$
967// \return \f$ c, s \rightarrow [c, -s; s, c]\f diagonalizes A$
968//
969template <typename T>
970std::pair<T, T> schur_sym(T const f, T const g, T const h) {
971 T c = 1.0;
972 T s = 0.0;
973
974 if (g != 0.0) {
975 T t = (h - f) / (2.0 * g);
976
977 if (t >= 0.0) {
978 t = 1.0 / (std::sqrt(1.0 + t * t) + t);
979 } else {
980 t = -1.0 / (std::sqrt(1.0 + t * t) - t);
981 }
982 c = 1.0 / std::sqrt(1.0 + t * t);
983 s = t * c;
984 }
985
986 return std::make_pair(c, s);
987}
988
989//
990// Givens rotation. [c, -s; s, c] [a; b] = [r; 0]
991// \param a, b
992// \return c, s
993//
994template <typename T> std::pair<T, T> givens(T const &a, T const &b) {
995 T c = 1.0;
996 T s = 0.0;
997
998 if (b != 0.0) {
999 if (std::abs(b) > std::abs(a)) {
1000 T const t = - a / b;
1001 s = 1.0 / std::sqrt(1.0 + t * t);
1002 c = t * s;
1003 } else {
1004 T const t = - b / a;
1005 c = 1.0 / std::sqrt(1.0 + t * t);
1006 s = t * c;
1007 }
1008 }
1009
1010 return std::make_pair(c, s);
1011}
1012
1013namespace impl {
1014
1015//
1016// R^N eigenvalue decomposition for symmetric 2nd-order tensor
1017// \param A tensor
1018// \return V eigenvectors, D eigenvalues in diagonal Matlab-style
1019// See algorithm 8.4.2 in Matrix Computations, Golub & Van Loan 1996
1020//
1021template <typename T, Index N>
1022std::pair<Tensor<T, N>, Tensor<T, N>> eig_sym_NxN(Tensor<T, N> const &A) {
1023 Tensor<T, N>
1024 D = sym(A);
1025
1026 Index const
1027 dimension = A.get_dimension();
1028
1029 Tensor<T, N>
1030 V = identity<T, N>(dimension);
1031
1032 T
1033 off = norm_off_diagonal(D);
1034
1035 T
1036 tol = machine_epsilon<T>() * norm(A);
1037
1038 // Estimate based on random generation and linear regression.
1039 // Golub & Van Loan p 429 expect ~ dimension * log(dimension)
1040 Index const
1041 max_iter = 5 * dimension * dimension / 2;
1042
1043 Index
1044 num_iter = 0;
1045
1046 while (off > tol && num_iter < max_iter) {
1047
1048 // Find largest off-diagonal entry
1049 Index
1050 p = 0;
1051
1052 Index
1053 q = 0;
1054
1055 std::tie(p, q) = arg_max_off_diagonal(D);
1056 if (p > q) {
1057 std::swap(p,q);
1058 }
1059
1060 // Obtain Givens rotations by using 2x2 symmetric Schur algorithm
1061 T const &
1062 f = D(p,p);
1063
1064 T const &
1065 g = D(p,q);
1066
1067 T const &
1068 h = D(q,q);
1069
1070 T
1071 c, s;
1072
1073 std::tie(c, s) = schur_sym(f, g, h);
1074
1075 // Apply Givens rotation to matrices
1076 // that are converging to eigenvalues and eigenvectors
1077 givens_left(c, s, p, q, D);
1078 givens_right(c, s, p, q, D);
1079
1080 givens_right(c, s, p, q, V);
1081
1082 off = norm_off_diagonal(D);
1083 num_iter++;
1084 }
1085
1086 Vector<T, N> d(dimension);
1087 Tensor<T, N> P(dimension);
1088
1089 std::tie(d, P) = sort_permutation(diag(D));
1090 D = diag(d);
1091 V = V * P;
1092
1093 return std::make_pair(V, D);
1094}
1095
1096//
1097// R^2 eigenvalue decomposition for symmetric 2nd-order tensor
1098// \param A tensor
1099// \return V eigenvectors, D eigenvalues in diagonal Matlab-style
1100//
1101template <typename T, Index N>
1102std::pair<Tensor<T, N>, Tensor<T, N>> eig_sym_2x2(Tensor<T, N> const &A) {
1103 assert(A.get_dimension() == 2);
1104
1105 T const f = A(0,0);
1106 T const g = 0.5 * (A(0,1) + A(1,0));
1107 T const h = A(1,1);
1108
1109 //
1110 // Eigenvalues, based on LAPACK's dlae2
1111 //
1112 T const sum = f + h;
1113 T const dif = std::abs(f - h);
1114 T const g2 = std::abs(g + g);
1115
1116 T fhmax = f;
1117 T fhmin = h;
1118
1119 const bool swap_diag = std::abs(h) > std::abs(f);
1120
1121 if (swap_diag == true) {
1122 std::swap(fhmax, fhmin);
1123 }
1124
1125 T r = 0.0;
1126 if (dif > g2) {
1127 T const t = g2 / dif;
1128 r = dif * std::sqrt(1.0 + t * t);
1129 } else if (dif < g2) {
1130 T const t = dif / g2;
1131 r = g2 * std::sqrt(1.0 + t * t);
1132 } else {
1133 // dif == g2, including zero
1134 r = g2 * std::sqrt(2.0);
1135 }
1136
1137 T s0 = 0.0;
1138 T s1 = 0.0;
1139
1140 if (sum != 0.0) {
1141 s0 = 0.5 * (sum + copysign(r, sum));
1142 // Order of execution important.
1143 // To get fully accurate smaller eigenvalue,
1144 // next line needs to be executed in higher precision.
1145 s1 = (fhmax / s0) * fhmin - (g / s0) * g;
1146 } else {
1147 // s0 == s1, including zero
1148 s0 = 0.5 * r;
1149 s1 = -0.5 * r;
1150 }
1151
1152 //
1153 // Eigenvectors. schur_sym returns the Jacobi rotation J = [c, s; -s, c]
1154 // whose columns are the eigenvectors and which diagonalizes A as
1155 // A = J * diag(l0, l1) * transpose(J), with the eigenvalue attached to
1156 // column 0 of J being l0 = f - tan(theta) * g and to column 1 being
1157 // l1 = h + tan(theta) * g (tan(theta) = s / c).
1158 //
1159 // The previous implementation used transpose(J) as V and paired the
1160 // magnitude-ordered eigenvalues (s0, s1) to the columns through swap_diag, so
1161 // V * D * transpose(V) did not reconstruct A for general off-diagonal cases
1162 // (Trilinos issue #15389). Build V = J directly, and attach the accurate
1163 // (s0, s1) eigenvalues to their matching columns.
1164 //
1165 T
1166 c, s;
1167
1168 std::tie(c, s) = schur_sym(f, g, h);
1169
1170 Tensor<T, N>
1171 V(c, s, -s, c);
1172
1173 T const l0 = f - (s / c) * g;
1174
1175 T d0, d1;
1176 if (std::abs(l0 - s0) <= std::abs(l0 - s1)) {
1177 d0 = s0;
1178 d1 = s1;
1179 } else {
1180 d0 = s1;
1181 d1 = s0;
1182 }
1183
1184 //
1185 // Return eigenvalues in descending order, permuting the eigenvectors to
1186 // match, for consistency with the eig_sym_NxN path. For 2x2 this is a single
1187 // compare-and-swap; done inline to avoid the heap allocation and matrix
1188 // multiply of the general sort_permutation, as eig_sym is called per
1189 // integration point in 2D mechanics.
1190 //
1191 if (d0 < d1) {
1192 std::swap(d0, d1);
1193 std::swap(V(0, 0), V(0, 1));
1194 std::swap(V(1, 0), V(1, 1));
1195 }
1196
1197 Tensor<T, N>
1198 D(d0, 0.0, 0.0, d1);
1199
1200 return std::make_pair(V, D);
1201}
1202
1203} // namespace impl
1204
1205//
1206// R^N eigenvalue decomposition for symmetric 2nd-order tensor
1207// \param A tensor
1208// \return V eigenvectors, D eigenvalues in diagonal Matlab-style
1209//
1210template <typename T, Index N>
1211std::pair<Tensor<T, N>, Tensor<T, N>> eig_sym(Tensor<T, N> const &A) {
1212 Index const
1213 dimension = A.get_dimension();
1214
1216 V(dimension), D(dimension);
1217
1218 switch (dimension) {
1219
1220 default:
1221 std::tie(V, D) = impl::eig_sym_NxN(A);
1222 break;
1223
1224 case 2:
1225 std::tie(V, D) = impl::eig_sym_2x2(A);
1226 break;
1227
1228 }
1229
1230 return std::make_pair(V, D);
1231}
1232
1233//
1234// R^N eigenvalue decomposition for SPD 2nd-order tensor
1235// \param A tensor
1236// \return V eigenvectors, D eigenvalues in diagonal Matlab-style
1237//
1238template <typename T, Index N>
1239std::pair<Tensor<T, N>, Tensor<T, N>> eig_spd(Tensor<T, N> const &A) {
1240 return eig_sym(A);
1241}
1242
1243//
1244// R^3 eigenvalue decomposition for SPD 2nd-order tensor
1245// \param A tensor
1246// \return V eigenvectors, D eigenvalues in diagonal Matlab-style
1247//
1248template <typename T, Index N>
1249std::pair<Tensor<T, N>, Tensor<T, N>> eig_spd_cos(Tensor<T, N> const &A) {
1250 Index const
1251 dimension = A.get_dimension();
1252
1253 assert(dimension == 3);
1254
1255 // This algorithm comes from the journal article
1256 // Scherzinger and Dohrmann, CMAME 197 (2008) 4007-4015
1257
1258 // this algorithm will return the eigenvalues in D
1259 // and the eigenvectors in V
1261 D = zero<T, N>(dimension);
1262
1264 V = zero<T, N>(dimension);
1265
1266 // not sure if this is necessary...
1267 T
1268 pi = std::acos(-1);
1269
1270 // convenience operators
1271 Tensor<T, N> const
1272 I = identity<T, N>(dimension);
1273
1274 int
1275 ii[3][2] = { { 1, 2 }, { 2, 0 }, { 0, 1 } };
1276
1278 rm = zero<T, N>(dimension);
1279
1280 // scale the matrix to reduce the characteristic equation
1281 T
1282 trA = (1.0/3.0) * I1(A);
1283
1285 Ap(A - trA*I);
1286
1287 // compute other invariants
1288 T
1289 J2 = I2(Ap);
1290
1291 T
1292 J3 = det(Ap);
1293
1294 // deal with volumetric tensors
1295 if (-J2 <= 1.e-30)
1296 {
1297 D(0,0) = trA;
1298 D(1,1) = trA;
1299 D(2,2) = trA;
1300
1301 V(0,0) = 1.0;
1302 V(1,0) = 0.0;
1303 V(2,0) = 0.0;
1304
1305 V(0,1) = 0.0;
1306 V(1,1) = 1.0;
1307 V(2,1) = 0.0;
1308
1309 V(0,2) = 0.0;
1310 V(1,2) = 0.0;
1311 V(2,2) = 1.0;
1312 }
1313 else
1314 {
1315 // first things first, find the most dominant e-value
1316 // Need to solve cos(3 theta)=rhs for theta
1317 T
1318 t1 = 3.0 / -J2;
1319
1320 T
1321 rhs = (J3 / 2.0) * T(std::sqrt(t1 * t1 * t1));
1322
1323 T
1324 theta = pi / 2.0 * (1.0 - (rhs < 0 ? -1.0 : 1.0));
1325
1326 if (std::abs(rhs) <= 1.0) theta = std::acos(rhs);
1327
1328 T
1329 thetad3 = theta / 3.0;
1330
1331 if (thetad3 > pi / 6.0) thetad3 += 2.0 * pi / 3.0;
1332
1333 // most dominant e-value
1334 D(2,2) = 2.0 * std::cos(thetad3) * std::sqrt(-J2 / 3.0);
1335
1336 // now reduce the system
1338 R = Ap - D(2,2) * I;
1339
1340 // QR factorization with column pivoting
1341 Vector<T, N> a(dimension);
1342 a(0) = R(0,0)*R(0,0) + R(1,0)*R(1,0) + R(2,0)*R(2,0);
1343 a(1) = R(0,1)*R(0,1) + R(1,1)*R(1,1) + R(2,1)*R(2,1);
1344 a(2) = R(0,2)*R(0,2) + R(1,2)*R(1,2) + R(2,2)*R(2,2);
1345
1346 // find the most dominant column
1347 int k = 0;
1348 T max = a(0);
1349 if (a(1) > max)
1350 {
1351 k = 1;
1352 max = a(1);
1353 }
1354 if (a(2) > max)
1355 {
1356 k = 2;
1357 }
1358
1359 // normalize the most dominant column to get s1
1360 a(k) = std::sqrt(a(k));
1361 for (int i(0); i < dimension; ++i)
1362 R(i,k) /= a(k);
1363
1364 // dot products of dominant column with other two columns
1365 T d0 = 0.0;
1366 T d1 = 0.0;
1367 for (int i(0); i < dimension; ++i)
1368 {
1369 d0 += R(i,k) * R(i,ii[k][0]);
1370 d1 += R(i,k) * R(i,ii[k][1]);
1371 }
1372
1373 // projection
1374 for (int i(0); i < dimension; ++i)
1375 {
1376 R(i,ii[k][0]) -= d0 * R(i,k);
1377 R(i,ii[k][1]) -= d1 * R(i,k);
1378 }
1379
1380 // now finding next most dominant column
1381 a.clear();
1382 for (int i(0); i < dimension; ++i)
1383 {
1384 a(0) += R(i,ii[k][0]) * R(i,ii[k][0]);
1385 a(1) += R(i,ii[k][1]) * R(i,ii[k][1]);
1386 }
1387
1388 int p = 0;
1389 if (std::abs(a(1)) > std::abs(a(0))) p = 1;
1390
1391 // normalize next most dominant column to get s2
1392 a(p) = std::sqrt(a(p));
1393 int k2 = ii[k][p];
1394
1395 for (int i(0); i < dimension; ++i)
1396 R(i,k2) /= a(p);
1397
1398 // set first eigenvector as cross product of s1 and s2
1399 V(0,2) = R(1,k) * R(2,k2) - R(2,k) * R(1,k2);
1400 V(1,2) = R(2,k) * R(0,k2) - R(0,k) * R(2,k2);
1401 V(2,2) = R(0,k) * R(1,k2) - R(1,k) * R(0,k2);
1402
1403 // normalize
1404 T
1405 mag = std::sqrt(V(0,2) * V(0,2) + V(1,2) * V(1,2) + V(2,2) * V(2,2));
1406
1407 V(0,2) /= mag;
1408 V(1,2) /= mag;
1409 V(2,2) /= mag;
1410
1411 // now for the other two eigenvalues, extract vectors
1413 rk(R(0,k), R(1,k), R(2,k));
1414
1416 rk2(R(0,k2), R(1,k2), R(2,k2));
1417
1418 // compute projections
1420 ak = Ap * rk;
1421
1423 ak2 = Ap * rk2;
1424
1425 // set up reduced remainder matrix
1426 rm(0,0) = dot(rk,ak);
1427 rm(0,1) = dot(rk,ak2);
1428 rm(1,1) = dot(rk2,ak2);
1429
1430 // compute eigenvalues 2 and 3
1431 T
1432 b = 0.5 * (rm(0,0) - rm(1,1));
1433
1434 T
1435 fac = (b < 0 ? -1.0 : 1.0);
1436
1437 T
1438 arg = b * b + rm(0,1) * rm(0,1);
1439
1440 if (arg == 0)
1441 D(0,0) = rm(1,1) + b;
1442 else
1443 D(0,0) = rm(1,1) + b - fac * std::sqrt(b * b + rm(0,1) * rm(0,1));
1444
1445 D(1,1) = rm(0,0) + rm(1,1) - D(0,0);
1446
1447 // update reduced remainder matrix
1448 rm(0,0) -= D(0,0);
1449 rm(1,0) = rm(0,1);
1450 rm(1,1) -= D(0,0);
1451
1452 // again, find most dominant column
1453 a.clear();
1454 a(0) = rm(0,0) * rm(0,0) + rm(0,1) * rm(0,1);
1455 a(1) = rm(0,1) * rm(0,1) + rm(1,1) * rm(1,1);
1456
1457 int k3 = 0;
1458 if (a(1) > a(0)) k3 = 1;
1459 if (a(k3) == 0.0)
1460 {
1461 rm(0,k3) = 1.0;
1462 rm(1,k3) = 0.0;
1463 }
1464
1465 // set 2nd eigenvector via cross product
1466 V(0,0) = rm(0,k3) * rk2(0) - rm(1,k3) * rk(0);
1467 V(1,0) = rm(0,k3) * rk2(1) - rm(1,k3) * rk(1);
1468 V(2,0) = rm(0,k3) * rk2(2) - rm(1,k3) * rk(2);
1469
1470 // normalize
1471 mag = std::sqrt(V(0,0) * V(0,0) + V(1,0) * V(1,0) + V(2,0) * V(2,0));
1472 V(0,0) /= mag;
1473 V(1,0) /= mag;
1474 V(2,0) /= mag;
1475
1476 // set last eigenvector as cross product of other two
1477 V(0,1) = V(1,0) * V(2,2) - V(2,0) * V(1,2);
1478 V(1,1) = V(2,0) * V(0,2) - V(0,0) * V(2,2);
1479 V(2,1) = V(0,0) * V(1,2) - V(1,0) * V(0,2);
1480
1481 // normalize
1482 mag = std::sqrt(V(0,1) * V(0,1) + V(1,1) * V(1,1) + V(2,1) * V(2,1));
1483 V(0,1) /= mag;
1484 V(1,1) /= mag;
1485 V(2,1) /= mag;
1486
1487 // add back in the offset
1488 for (int i(0); i < dimension; ++i)
1489 D(i,i) += trA;
1490 }
1491
1492 return std::make_pair(V, D);
1493}
1494
1495//
1496// Cholesky decomposition, rank-1 update algorithm
1497// (Matrix Computations 3rd ed., Golub & Van Loan, p145)
1498// \param A assumed symmetric tensor
1499// \return G Cholesky factor A = GG^T
1500// \return completed (bool) algorithm ran to completion
1501//
1502template <typename T, Index N>
1503std::pair<Tensor<T, N>, bool> cholesky(Tensor<T, N> const &A) {
1505 G = sym(A);
1506
1507 Index const
1508 dimension = A.get_dimension();
1509
1510 for (Index k = 0; k < dimension; ++k) {
1511
1512 // Zeros above the diagonal
1513 for (Index j = k + 1; j < dimension; ++j) {
1514 G(k,j) = 0.0;
1515 }
1516
1517 T
1518 s = G(k,k);
1519
1520 if (s <= 0.0) {
1521 return std::make_pair(G, false);
1522 }
1523
1524 s = std::sqrt(s);
1525
1526 for (Index j = k + 1; j < dimension; ++j) {
1527 G(j,k) /= s;
1528 }
1529
1530 G(k,k) = s;
1531
1532 for (Index j = k + 1; j < dimension; ++j) {
1533 for (Index i = j; i < dimension; ++i) {
1534 G(i,j) -= G(i,k) * G(j,k);
1535 }
1536 }
1537
1538 }
1539
1540 return std::make_pair(G, true);
1541}
1542
1544} // namespace minitensor
1545
1546#endif // MiniTensor_Factorizations_h
#define MT_WARNING(...)
#define KOKKOS_INLINE_FUNCTION
#define MT_ERROR_EXIT(...)
KOKKOS_INLINE_FUNCTION void clear()
KOKKOS_INLINE_FUNCTION Index get_dimension() const
KOKKOS_INLINE_FUNCTION Index get_dimension() const
KOKKOS_INLINE_FUNCTION Matrix< typename Promote< S, T >::type, M, N > dot_t(Matrix< S, M, P > const &A, Matrix< T, N, P > const &B)
KOKKOS_INLINE_FUNCTION Tensor< T, N > diag(Vector< T, N > const &v)
KOKKOS_INLINE_FUNCTION void scale(TensorBase< R, SR > const &A, S const &s, TensorBase< T, ST > &B)
KOKKOS_INLINE_FUNCTION Vector< typename Promote< S, T >::type, M > dot(Matrix< T, M, N > const &A, Vector< S, N > const &u)
KOKKOS_INLINE_FUNCTION Matrix< typename Promote< S, T >::type, M, N > t_dot(Matrix< S, P, M > const &A, Matrix< T, P, N > const &B)
KOKKOS_INLINE_FUNCTION Matrix< T, M, N > transpose(Matrix< T, N, M > const &A)
KOKKOS_INLINE_FUNCTION Tensor< T, N > sym(Tensor< T, N > const &A)
std::pair< Tensor< T, N >, Tensor< T, N > > eig_spd(Tensor< T, N > const &A)
std::tuple< Tensor< T, N >, Tensor< T, N >, Tensor< T, N > > polar_left_logV_lame(Tensor< T, N > const &F)
std::pair< Tensor< T, N >, Tensor< T, N > > polar_left_eig(Tensor< T, N > const &A)
std::tuple< Tensor< T, N >, Tensor< T, N >, Tensor< T, N > > polar_left_logV(Tensor< T, N > const &F)
std::pair< T, T > givens(T const &a, T const &b)
std::pair< T, T > schur_sym(const T f, const T g, const T h)
std::pair< Tensor< T, N >, Tensor< T, N > > polar_right_eig(Tensor< T, N > const &A)
std::tuple< Tensor< T, N >, Tensor< T, N >, Tensor< T, N > > polar_left_logV_eig(Tensor< T, N > const &F)
T cond(Tensor< T, N > const &A)
std::pair< Tensor< T, N >, Tensor< T, N > > eig_sym(Tensor< T, N > const &A)
KOKKOS_INLINE_FUNCTION Tensor< T, N > polar_rotation(Tensor< T, N > const &A)
std::tuple< Tensor< T, N >, Tensor< T, N >, Tensor< T, N > > svd(Tensor< T, N > const &A)
std::pair< Tensor< T, N >, Tensor< T, N > > polar_right(Tensor< T, N > const &A)
KOKKOS_INLINE_FUNCTION void givens_right(T const &c, T const &s, Index i, Index k, Tensor< T, N > &A)
std::pair< Tensor< T, N >, Tensor< T, N > > polar_left(Tensor< T, N > const &A)
KOKKOS_INLINE_FUNCTION void givens_left(T const &c, T const &s, Index i, Index k, Tensor< T, N > &A)
std::pair< Tensor< T, N >, Tensor< T, N > > eig_spd_cos(Tensor< T, N > const &A)
T inv_cond(Tensor< T, N > const &A)
std::pair< Tensor< T, N >, bool > cholesky(Tensor< T, N > const &A)
std::pair< Vector< T, N >, Tensor< T, N > > sort_permutation(Vector< T, N > const &u)
KOKKOS_INLINE_FUNCTION Tensor< T, N > inverse(Tensor< T, N > const &A)
KOKKOS_INLINE_FUNCTION T I1(Tensor< T, N > const &A)
KOKKOS_INLINE_FUNCTION T norm(Tensor< T, N > const &A)
KOKKOS_INLINE_FUNCTION T det(Tensor< T, N > const &A)
KOKKOS_INLINE_FUNCTION T norm_off_diagonal(Tensor< T, N > const &A)
KOKKOS_INLINE_FUNCTION T norm_1(Tensor< T, N > const &A)
std::pair< Index, Index > arg_max_off_diagonal(Tensor< T, N > const &A)
KOKKOS_INLINE_FUNCTION T norm_infinity(Tensor< T, N > const &A)
KOKKOS_INLINE_FUNCTION T I2(Tensor< T, N > const &A)
uint32_t Index
Indexing type.
KOKKOS_INLINE_FUNCTION Sacado::ScalarType< T >::type tau()
KOKKOS_INLINE_FUNCTION int sgn(T const &s)
KOKKOS_INLINE_FUNCTION T abs(T const &a)
KOKKOS_INLINE_FUNCTION T max(T const &a, T const &b)
KOKKOS_INLINE_FUNCTION T copysign(T const &a, T const &b)