Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
tuple_tensor_dual_functions.hpp
1 // Copyright (c) Lawrence Livermore National Security, LLC and
2 // other Smith Project Developers. See the top-level LICENSE file for
3 // details.
4 //
5 // SPDX-License-Identifier: (BSD-3-Clause)
6 
7 #pragma once
8 
9 #include "mfem.hpp"
10 
14 
15 namespace smith {
16 
18 template <typename T>
20  static constexpr bool value = false;
21 };
22 
24 template <typename T, int... n>
26  static constexpr bool value = true;
27 };
28 
37 template <typename S, typename T, int m, int... n,
38  typename = std::enable_if_t<std::is_arithmetic_v<S> || is_dual_number<S>::value>>
39 SMITH_HOST_DEVICE constexpr auto operator*(S scale, const tensor<T, m, n...>& A)
40 {
41  tensor<decltype(S{} * T{}), m, n...> C{};
42  for (int i = 0; i < m; i++) {
43  C[i] = scale * A[i];
44  }
45  return C;
46 }
47 
56 template <typename S, typename T, int m, int... n,
57  typename = std::enable_if_t<std::is_arithmetic_v<S> || is_dual_number<S>::value>>
58 SMITH_HOST_DEVICE constexpr auto operator*(const tensor<T, m, n...>& A, S scale)
59 {
60  tensor<decltype(T{} * S{}), m, n...> C{};
61  for (int i = 0; i < m; i++) {
62  C[i] = A[i] * scale;
63  }
64  return C;
65 }
66 
75 template <typename S, typename T, int m, int... n,
76  typename = std::enable_if_t<std::is_arithmetic_v<S> || is_dual_number<S>::value>>
77 SMITH_HOST_DEVICE constexpr auto operator/(S scale, const tensor<T, m, n...>& A)
78 {
79  tensor<decltype(S{} * T{}), n...> C{};
80  for (int i = 0; i < m; i++) {
81  C[i] = scale / A[i];
82  }
83  return C;
84 }
85 
94 template <typename S, typename T, int m, int... n,
95  typename = std::enable_if_t<std::is_arithmetic_v<S> || is_dual_number<S>::value>>
96 SMITH_HOST_DEVICE constexpr auto operator/(const tensor<T, m, n...>& A, S scale)
97 {
98  tensor<decltype(T{} * S{}), m, n...> C{};
99  for (int i = 0; i < m; i++) {
100  C[i] = A[i] / scale;
101  }
102  return C;
103 }
104 
106 template <int i, typename S, typename T>
107 struct one_hot_helper;
108 
109 template <int i, int... I, typename T>
110 struct one_hot_helper<i, std::integer_sequence<int, I...>, T> {
111  using type = tuple<std::conditional_t<i == I, T, zero>...>;
112 };
113 
114 template <int i, int n, typename T>
115 struct one_hot : public one_hot_helper<i, std::make_integer_sequence<int, n>, T> {};
117 
124 template <int i, int n, typename T>
126 
128 template <int i, int N>
129 SMITH_HOST_DEVICE constexpr auto make_dual_helper(zero /*arg*/)
130 {
131  return zero{};
132 }
133 
141 template <int i, int N>
142 SMITH_HOST_DEVICE constexpr auto make_dual_helper(double arg)
143 {
144  using gradient_t = one_hot_t<i, N, double>;
145  dual<gradient_t> arg_dual{};
146  arg_dual.value = arg;
147  smith::get<i>(arg_dual.gradient) = 1.0;
148  return arg_dual;
149 }
150 
158 template <int i, int N, typename T, int... n>
160 {
161  using gradient_t = one_hot_t<i, N, tensor<T, n...>>;
162  tensor<dual<gradient_t>, n...> arg_dual{};
163  for_constexpr<n...>([&](auto... j) {
164  arg_dual(j...).value = arg(j...);
165  smith::get<i>(arg_dual(j...).gradient)(j...) = 1.0;
166  });
167  return arg_dual;
168 }
169 
187 template <typename T0, typename T1>
188 SMITH_HOST_DEVICE constexpr auto make_dual(const tuple<T0, T1>& args)
189 {
190  return tuple{make_dual_helper<0, 2>(get<0>(args)), make_dual_helper<1, 2>(get<1>(args))};
191 }
192 
194 template <typename T0, typename T1, typename T2>
195 SMITH_HOST_DEVICE constexpr auto make_dual(const tuple<T0, T1, T2>& args)
196 {
197  return tuple{make_dual_helper<0, 3>(get<0>(args)), make_dual_helper<1, 3>(get<1>(args)),
198  make_dual_helper<2, 3>(get<2>(args))};
199 }
200 
208 template <bool dualify, typename T>
210 {
211  if constexpr (dualify) {
212  return make_dual(x);
213  }
214  if constexpr (!dualify) {
215  return x;
216  }
217 }
218 
227 template <bool dualify, typename T, int n>
229 {
230  if constexpr (dualify) {
231  using return_type = decltype(make_dual(T{}));
232  tensor<return_type, n> output;
233  for (int i = 0; i < n; i++) {
234  output[i] = make_dual(x[i]);
235  }
236  return output;
237  }
238  if constexpr (!dualify) {
239  return x;
240  }
241 }
242 
244 template <int n, typename... T, int... i>
245 SMITH_HOST_DEVICE constexpr auto make_dual_helper(const smith::tuple<T...>& args, std::integer_sequence<int, i...>)
246 {
247  // Sam: it took me longer than I'd like to admit to find this issue, so here's an explanation
248  //
249  // note: we use smith::make_tuple(...) instead of smith::tuple{...} here because if
250  // the first argument passed in is of type `smith::tuple < smith::tuple < T ... > >`
251  // then doing something like
252  //
253  // smith::tuple{smith::get<i>(args)...};
254  //
255  // will be expand to something like
256  //
257  // smith::tuple{smith::tuple< T ... >{}};
258  //
259  // which invokes the copy ctor, returning a `smith::tuple< T ... >`
260  // instead of `smith::tuple< smith::tuple < T ... > >`
261  //
262  // but smith::make_tuple(smith::get<i>(args)...) will never accidentally trigger the copy ctor
263  return smith::make_tuple(promote_to_dual_when < i == n > (smith::get<i>(args))...);
264 }
265 
273 template <int n, typename... T>
274 constexpr auto make_dual_wrt(const smith::tuple<T...>& args)
275 {
276  return make_dual_helper<n>(args, std::make_integer_sequence<int, static_cast<int>(sizeof...(T))>{});
277 }
278 
288 template <typename T1, typename T2, int n>
290 {
291  tensor<decltype(get_value(tuple<T1, T2>{})), n> output{};
292  for (int i = 0; i < n; i++) {
293  output[i] = get_value(input[i]);
294  }
295  return output;
296 }
297 
303 template <typename... T>
304 SMITH_HOST_DEVICE auto get_value(const smith::tuple<T...>& tuple_of_values)
305 {
306  return smith::apply([](const auto&... each_value) { return smith::tuple{get_value(each_value)...}; },
307  tuple_of_values);
308 }
309 
314 template <typename... T>
316 {
317  return smith::apply([](auto... each_value) { return smith::tuple{each_value...}; }, arg.gradient);
318 }
319 
321 template <typename... T, int... n>
323 {
324  smith::tuple<outer_product_t<tensor<double, n...>, T>...> g{};
325  for_constexpr<n...>([&](auto... i) {
326  for_constexpr<sizeof...(T)>([&](auto j) { smith::get<j>(g)(i...) = smith::get<j>(arg(i...).gradient); });
327  });
328  return g;
329 }
330 
332 template <typename... T>
334 {
335  return smith::apply([](auto... each_value) { return smith::tuple{get_gradient(each_value)...}; }, tuple_of_values);
336 }
337 
343 template <int... n>
345 {
346  tensor<dual<tensor<double, n...>>, n...> A_dual{};
347  for_constexpr<n...>([&](auto... i) {
348  A_dual(i...).value = A(i...);
349  A_dual(i...).gradient(i...) = 1.0;
350  });
351  return A_dual;
352 }
353 
363 template <typename T, int n>
365 {
366  constexpr auto abs = [](double x) { return (x < 0) ? -x : x; };
367  constexpr auto swap = [](auto& x, auto& y) {
368  auto tmp = x;
369  x = y;
370  y = tmp;
371  };
372 
373  auto U = A;
374  // initialize L to Identity
375  auto L = tensor<T, n, n>{};
376  // This handles the case if T is a dual number
377  // TODO - BT: make a dense identity that is templated on type
378  for (int i = 0; i < n; i++) {
379  if constexpr (is_dual_number<T>::value) {
380  L[i][i].value = 1.0;
381  } else {
382  L[i][i] = 1.0;
383  }
384  }
385  tensor<int, n> P(make_tensor<n>([](auto i) { return i; }));
386 
387  for (int i = 0; i < n; i++) {
388  // Search for maximum in this column
389  double max_val = abs(get_value(U[i][i]));
390 
391  int max_row = i;
392  for (int j = i + 1; j < n; j++) {
393  auto U_ji = get_value(U[j][i]);
394  if (abs(U_ji) > max_val) {
395  max_val = abs(U_ji);
396  max_row = j;
397  }
398  }
399 
400  swap(P[max_row], P[i]);
401  swap(U[max_row], U[i]);
402  }
403 
404  for (int i = 0; i < n; i++) {
405  // zero entries below in this column in U
406  // and fill in L entries
407  for (int j = i + 1; j < n; j++) {
408  auto c = U[j][i] / U[i][i];
409  L[j][i] = c;
410  U[j] -= c * U[i];
411  U[j][i] = T{};
412  }
413  }
414 
415  return {P, L, U};
416 }
417 
424 template <typename S, typename T, int n, int... m>
426 {
427  // We want to avoid accumulating the derivative through the
428  // LU factorization, because it is computationally expensive.
429  // Instead, we perform the LU factorization on the values of
430  // A, and then two backsolves: one to compute the primal (x),
431  // and another to compute its derivative (dx).
432  // If A is not dual, the second solve is a no-op.
433 
434  // Strip off derivatives, if any, and compute only x (ie no derivative)
435  auto lu_factors = factorize_lu(get_value(A));
436  auto x = linear_solve(lu_factors, get_value(b));
437 
438  // Compute directional derivative of x.
439  // If both b and A are not dual, the zero type
440  // makes these no-ops.
441  auto r = get_gradient(b) - dot(get_gradient(A), x);
442  auto dx = linear_solve(lu_factors, r);
443 
444  if constexpr (is_zero<decltype(dx)>{}) {
445  return x;
446  } else {
447  return make_dual(x, dx);
448  }
449 }
450 
454 template <typename T, int n>
455 SMITH_HOST_DEVICE constexpr auto make_dual(const tensor<T, n>& x, const tensor<T, n>& dx)
456 {
457  return make_tensor<n>([&](int i) { return dual<T>{x[i], dx[i]}; });
458 }
459 
463 template <typename T, int m, int n>
464 SMITH_HOST_DEVICE constexpr auto make_dual(const tensor<T, m, n>& x, const tensor<T, m, n>& dx)
465 {
466  return make_tensor<m, n>([&](int i, int j) { return dual<T>{x[i][j], dx[i][j]}; });
467 }
468 
478 template <typename gradient_type, int n>
480 {
481  auto invA = inv(get_value(A));
482  return make_tensor<n, n>([&](int i, int j) {
483  auto value = invA[i][j];
484  gradient_type gradient{};
485  for (int k = 0; k < n; k++) {
486  for (int l = 0; l < n; l++) {
487  gradient -= invA[i][k] * A[k][l].gradient * invA[l][j];
488  }
489  }
490  return dual<gradient_type>{value, gradient};
491  });
492 }
493 
498 template <typename T, int... n>
499 SMITH_HOST_DEVICE auto get_value(const tensor<dual<T>, n...>& arg)
500 {
501  tensor<double, n...> value{};
502  for_constexpr<n...>([&](auto... i) { value(i...) = arg(i...).value; });
503  return value;
504 }
505 
510 template <int... n>
511 SMITH_HOST_DEVICE constexpr auto get_gradient(const tensor<dual<double>, n...>& arg)
512 {
513  tensor<double, n...> g{};
514  for_constexpr<n...>([&](auto... i) { g(i...) = arg(i...).gradient; });
515  return g;
516 }
517 
519 template <int... n, int... m>
521 {
522  tensor<double, n..., m...> g{};
523  for_constexpr<n...>([&](auto... i) { g(i...) = arg(i...).gradient; });
524  return g;
525 }
526 
530 struct SolverStatus {
531  bool converged;
532  unsigned int iterations;
533  double residual;
534 };
535 
540  double xtol;
541  double rtol;
542  unsigned int max_iter;
543 };
544 
546 const ScalarSolverOptions default_solver_options{.xtol = 1e-8, .rtol = 0, .max_iter = 25};
547 
575 template <typename function, typename... ParamTypes>
576 auto solve_scalar_equation(const function& f, double x0, double lower_bound, double upper_bound,
577  ScalarSolverOptions options, ParamTypes... params)
578 {
579  double x, df_dx;
580  double fl = f(lower_bound, get_value(params)...);
581  double fh = f(upper_bound, get_value(params)...);
582 
583  SLIC_ERROR_ROOT_IF(fl * fh > 0, "solve_scalar_equation: root not bracketed by input bounds.");
584 
585  unsigned int iterations = 0;
586  bool converged = false;
587 
588  // handle corner cases where one of the brackets is the root
589  if (fl == 0) {
590  x = lower_bound;
591  converged = true;
592  } else if (fh == 0) {
593  x = upper_bound;
594  converged = true;
595  }
596 
597  if (converged) {
598  df_dx = get_gradient(f(make_dual(x), get_value(params)...));
599 
600  } else {
601  // orient search so that f(xl) < 0
602  double xl = lower_bound;
603  double xh = upper_bound;
604  if (fl > 0) {
605  xl = upper_bound;
606  xh = lower_bound;
607  }
608 
609  // move initial guess if it is not between brackets
610  if (x0 < lower_bound || x0 > upper_bound) {
611  x0 = 0.5 * (lower_bound + upper_bound);
612  }
613 
614  x = x0;
615  double delta_x_old = std::abs(upper_bound - lower_bound);
616  double delta_x = delta_x_old;
617  auto R = f(make_dual(x), get_value(params)...);
618  auto fval = get_value(R);
619  df_dx = get_gradient(R);
620 
621  while (!converged) {
622  if (iterations == options.max_iter) {
623  SLIC_WARNING("solve_scalar_equation failed to converge in allotted iterations.");
624  break;
625  }
626 
627  // use bisection if Newton oversteps brackets or is not decreasing sufficiently
628  if ((x - xh) * df_dx - fval > 0 || (x - xl) * df_dx - fval < 0 ||
629  std::abs(2. * fval) > std::abs(delta_x_old * df_dx)) {
630  delta_x_old = delta_x;
631  delta_x = 0.5 * (xh - xl);
632  x = xl + delta_x;
633  converged = (x == xl);
634  } else { // use Newton step
635  delta_x_old = delta_x;
636  delta_x = fval / df_dx;
637  auto temp = x;
638  x -= delta_x;
639  converged = (x == temp);
640  }
641 
642  // function and jacobian evaluation
643  R = f(make_dual(x), get_value(params)...);
644  fval = get_value(R);
645  df_dx = get_gradient(R);
646 
647  // convergence check
648  converged = converged || (std::abs(delta_x) < options.xtol) || (std::abs(fval) < options.rtol);
649 
650  // maintain bracket on root
651  if (fval < 0) {
652  xl = x;
653  } else {
654  xh = x;
655  }
656 
657  ++iterations;
658  }
659  }
660 
661  // Accumulate derivatives so that the user can get derivatives
662  // with respect to parameters, subject to constraing that f(x, p) = 0 for all p
663  // Conceptually, we're doing the following:
664  // [fval, df_dp] = f(get_value(x), p)
665  // df = 0
666  // for p in params:
667  // df += inner(df_dp, dp)
668  // dx = -df / df_dx
669  constexpr bool contains_duals =
671  if constexpr (contains_duals) {
672  auto [fval, df] = f(x, params...);
673  auto dx = -df / df_dx;
674  SolverStatus status{.converged = converged, .iterations = iterations, .residual = fval};
675  return tuple{dual{x, dx}, status};
676  }
677  if constexpr (!contains_duals) {
678  auto fval = f(x, params...);
679  SolverStatus status{.converged = converged, .iterations = iterations, .residual = fval};
680  return tuple{x, status};
681  }
682 }
683 
696 template <typename function, int n>
697 auto find_root(const function& f, tensor<double, n> x0)
698 {
699  static_assert(std::is_same_v<decltype(f(x0)), tensor<double, n>>,
700  "error: f(x) must have the same number of equations as unknowns");
701 
702  double epsilon = 1.0e-8;
703  int max_iterations = 10;
704 
705  auto x = x0;
706 
707  for (int k = 0; k < max_iterations; k++) {
708  auto output = f(make_dual(x));
709  auto r = get_value(output);
710  if (norm(r) < epsilon) break;
711  auto J = get_gradient(output);
712  x -= linear_solve(J, r);
713  }
714 
715  return x;
716 };
717 
726 template <typename T, int size>
728 {
729  // put tensor values in an mfem::DenseMatrix
730  mfem::DenseMatrix matA(size, size);
731  for (int i = 0; i < size; i++) {
732  for (int j = 0; j < size; j++) {
733  if constexpr (is_dual_number<T>::value) {
734  matA(i, j) = A[i][j].value;
735  } else {
736  matA(i, j) = A[i][j];
737  }
738  }
739  }
740 
741  // compute eigendecomposition
742  mfem::DenseMatrixEigensystem eig_sys(matA);
743  eig_sys.Eval();
744 
745  smith::tensor<T, size> output;
746 
747  for (int k = 0; k < size; k++) {
748  // extract eigenvalues
749  output[k] = eig_sys.Eigenvalue(k);
750 
751  // and calculate their derivatives, when appropriate
752  if constexpr (is_dual_number<T>::value) {
753  tensor<double, size> phi = make_tensor<size>([&](int i) { return eig_sys.Eigenvector(k)[i]; });
754  auto dA = make_tensor<size, size>([&](int i, int j) { return A(i, j).gradient; });
755  output[k].gradient = dot(phi, dA, phi);
756  }
757  }
758 
759  return output;
760 }
761 
768 template <typename T>
769 int sgn(T val)
770 {
771  // Should we implement the derivative?
772  // It should be NaN when val = 0
773  return (T(0) < val) - (val < T(0));
774 }
775 
782 template <typename T>
784 {
785  auto swap = [](int& first, int& second) {
786  int tmp = first;
787  first = second;
788  second = tmp;
789  };
790  tensor<int, 3> order{0, 1, 2};
791  if (v[0] > v[1]) swap(order[0], order[1]);
792  if (v[order[1]] > v[order[2]]) swap(order[1], order[2]);
793  if (v[order[0]] > v[order[1]]) swap(order[0], order[1]);
794  return order;
795 }
796 
808 {
809  // We know of optimizations for this routine. When this becomes the
810  // bottleneck, we can revisit. See OptimiSM for details.
811 
812  tensor<double, 3> eta{};
813  tensor<double, 3, 3> Q = DenseIdentity<3>();
814 
815  auto A_dev = dev(A);
816  double J2 = 0.5 * inner(A_dev, A_dev);
817  double J3 = det(A_dev);
818 
819  if (J2 > 0.0) {
820  // angle used to find eigenvalues
821  double tmp = (0.5 * J3) * std::pow(3.0 / J2, 1.5);
822  double alpha = std::acos(fmin(fmax(tmp, -1.0), 1.0)) / 3.0;
823 
824  // consider the most distinct eigenvalue first
825  if (6.0 * alpha < M_PI) {
826  eta[0] = 2 * std::sqrt(J2 / 3.0) * std::cos(alpha);
827  } else {
828  eta[0] = 2 * std::sqrt(J2 / 3.0) * std::cos(alpha + 2.0 * M_PI / 3.0);
829  }
830 
831  // find the eigenvector for that eigenvalue
832  mat3 r;
833 
834  int imax = -1;
835  double norm_max = -1.0;
836 
837  for (int i = 0; i < 3; i++) {
838  for (int j = 0; j < 3; j++) {
839  r[i][j] = A_dev(j, i) - (i == j) * eta(0);
840  }
841 
842  double norm_r = norm(r[i]);
843  if (norm_max < norm_r) {
844  imax = i;
845  norm_max = norm_r;
846  }
847  }
848 
849  vec3 s0, s1, t1, t2, v0, v1, v2, w;
850 
851  s0 = normalize(r[imax]);
852  t1 = r[(imax + 1) % 3] - dot(r[(imax + 1) % 3], s0) * s0;
853  t2 = r[(imax + 2) % 3] - dot(r[(imax + 2) % 3], s0) * s0;
854  s1 = normalize((norm(t1) > norm(t2)) ? t1 : t2);
855 
856  // record the first eigenvector
857  v0 = cross(s0, s1);
858  for (int i = 0; i < 3; i++) {
859  Q[i][0] = v0[i];
860  }
861 
862  // get the other two eigenvalues by solving the
863  // remaining quadratic characteristic polynomial
864  auto A_dev_s0 = dot(A_dev, s0);
865  auto A_dev_s1 = dot(A_dev, s1);
866 
867  double A11 = dot(s0, A_dev_s0);
868  double A12 = dot(s0, A_dev_s1);
869  double A21 = A12;
870  double A22 = dot(s1, A_dev_s1);
871 
872  double delta = 0.5 * std::sqrt((A11 - A22) * (A11 - A22) + 4 * A12 * A21);
873 
874  eta(1) = 0.5 * (A11 + A22) - delta;
875  eta(2) = 0.5 * (A11 + A22) + delta;
876 
877  // if the remaining eigenvalues are exactly the same
878  // then just use the basis for the orthogonal complement
879  // found earlier
880  if (fabs(delta) <= 1.0e-15) {
881  for (int i = 0; i < 3; i++) {
882  Q[i][1] = s0(i);
883  Q[i][2] = s1(i);
884  }
885 
886  // otherwise compute the remaining eigenvectors
887  } else {
888  t1 = A_dev_s0 - eta(1) * s0;
889  t2 = A_dev_s1 - eta(1) * s1;
890 
891  w = normalize((norm(t1) > norm(t2)) ? t1 : t2);
892 
893  v1 = normalize(cross(w, v0));
894  for (int i = 0; i < 3; i++) Q[i][1] = v1(i);
895 
896  // define the last eigenvector as
897  // the direction perpendicular to the
898  // first two directions
899  v2 = normalize(cross(v0, v1));
900  for (int i = 0; i < 3; i++) Q[i][2] = v2(i);
901  }
902  }
903  // eta are actually eigenvalues of A_dev, so
904  // shift them to get eigenvalues of A
905  for (int i = 0; i < 3; i++) eta[i] += tr(A) / 3.0;
906 
907  // sort eigenvalues into ascending order
908  auto order = argsort(eta);
909  vec3 eigvals{{eta[order[0]], eta[order[1]], eta[order[2]]}};
910  // clang-format off
911  mat3 eigvecs{{{Q[0][order[0]], Q[0][order[1]], Q[0][order[2]]},
912  {Q[1][order[0]], Q[1][order[1]], Q[1][order[2]]},
913  {Q[2][order[0]], Q[2][order[1]], Q[2][order[2]]}}};
914  // clang-format on
915 
916  return {eigvals, eigvecs};
917 }
918 
919 /*
920 // Should we provide this fallback, or force the author to consider how to
921 // write a numerically stable version on a case-by-case basis?
922 // The convenience of this is somewhat undermined by the fact that it would
923 // only work for functions that already have a dual number overload.
924 template <typename Function>
925 double generic_eigenvalue_tangent(double lam1, double lam2, const Function& f)
926 {
927  if (lam1 == lam2) {
928  return f(make_dual(lam1));
929  } else {
930  return (f(lam1) - f(lam2))/(lam1 - lam2);
931  }
932 }
933 */
934 
967 template <typename T, typename Function, typename EigvalSecantFunction>
968 auto symmetric_mat3_function(tensor<T, 3, 3> A, const Function& f, const EigvalSecantFunction& g)
969 {
970  auto [lambda, Q] = eig_symm(get_value(A));
971  vec3 y;
972  for (int i = 0; i < 3; i++) {
973  y[i] = f(lambda[i]);
974  }
975  auto f_A = dot(Q, dot(diag(y), transpose(Q)));
976 
977  if constexpr (!is_dual_number<T>::value) {
978  return f_A;
979  } else {
980  return symmetric_mat3_function_with_derivative(A, f_A, lambda, Q, g);
981  }
982 }
983 
985 template <typename Gradient, typename Function>
987  tensor<double, 3, 3> f_A, vec3 lambda, mat3 Q,
988  const Function& g)
989 {
990  return make_tensor<3, 3>([&](int i, int j) {
991  auto value = f_A[i][j];
992  Gradient gradient{};
993  for (int k = 0; k < 3; k++) {
994  for (int l = 0; l < 3; l++) {
995  for (int a = 0; a < 3; a++) {
996  for (int b = 0; b < 3; b++) {
997  gradient += g(lambda[a], lambda[b]) * Q[k][a] * Q[l][b] * Q[i][a] * Q[j][b] * A[k][l].gradient;
998  }
999  }
1000  }
1001  }
1002  return dual<Gradient>{value, gradient};
1003  });
1004 }
1005 
1012 template <typename T>
1014 {
1015  auto g = [](double lam1, double lam2) {
1016  if (lam1 == lam2) {
1017  return 1 / lam1;
1018  } else {
1019  double y = lam1 / lam2;
1020  return (std::log(y) / (y - 1.0)) / lam2;
1021  }
1022  };
1023  return symmetric_mat3_function(A, [](double x) { return std::log(x); }, g);
1024 }
1025 
1032 template <typename T>
1034 {
1035  auto g = [](double lam1, double lam2) {
1036  if (lam1 == lam2) {
1037  return std::exp(lam1);
1038  } else {
1039  double arg = lam1 - lam2;
1040  return std::exp(lam2) * std::expm1(arg) / arg;
1041  }
1042  };
1043  return symmetric_mat3_function(A, [](double x) { return std::exp(x); }, g);
1044 }
1045 
1052 template <typename T>
1054 {
1055  auto g = [](double lam1, double lam2) { return 1.0 / (std::sqrt(lam1) + std::sqrt(lam2)); };
1056  return symmetric_mat3_function(A, [](double x) { return std::sqrt(x); }, g);
1057 }
1058 
1059 } // namespace smith
#define SMITH_HOST_DEVICE
Macro that evaluates to __host__ __device__ when compiling with nvcc or amdclang and does nothing on ...
Definition: accelerator.hpp:37
This file contains the declaration of a dual number class.
constexpr SMITH_HOST_DEVICE void for_constexpr(const lambda &f)
multidimensional loop tool that evaluates the lambda body inside the innermost loop.
Accelerator functionality.
Definition: smith.cpp:36
SMITH_HOST_DEVICE auto log(dual< gradient_type > a)
implementation of the natural logarithm function for dual numbers
Definition: dual.hpp:389
const ScalarSolverOptions default_solver_options
Default options for solve_scalar_equation.
SMITH_HOST_DEVICE auto promote_to_dual_when(const T &x)
a function that optionally (decided at compile time) converts a value to its dual type
SMITH_HOST_DEVICE auto acos(dual< gradient_type > a)
implementation of acos for dual numbers
Definition: dual.hpp:373
SMITH_HOST_DEVICE auto exp(dual< gradient_type > a)
implementation of exponential function for dual numbers
Definition: dual.hpp:381
SMITH_HOST_DEVICE auto apply(lambda f, tuple< T... > &args)
a way of passing an n-tuple to a function that expects n separate arguments
Definition: tuple.hpp:779
auto solve_scalar_equation(const function &f, double x0, double lower_bound, double upper_bound, ScalarSolverOptions options, ParamTypes... params)
Solves a nonlinear scalar-valued equation and gives derivatives of solution to parameters.
auto eigenvalues(const smith::tensor< T, size, size > &A)
compute the eigenvalues of a symmetric matrix A
constexpr SMITH_HOST_DEVICE auto operator*(const dual< gradient_type > &a, double b)
multiplication of a dual number and a non-dual number
Definition: dual.hpp:108
constexpr SMITH_HOST_DEVICE auto inv(const isotropic_tensor< T, m, m > &I)
return the inverse of an isotropic tensor
SMITH_HOST_DEVICE auto cross(const tensor< T, 3, 2 > &A)
compute the cross product of the columns of A: A(:,1) x A(:,2)
Definition: tensor.hpp:966
auto log_symm(tensor< T, 3, 3 > A)
Logarithm of a symmetric matrix.
SMITH_HOST_DEVICE auto sqrt(dual< gradient_type > x)
implementation of square root for dual numbers
Definition: dual.hpp:308
constexpr SMITH_HOST_DEVICE auto norm(const isotropic_tensor< T, m, m > &I)
compute the Frobenius norm (sqrt(tr(dot(transpose(I), I)))) of an isotropic tensor
constexpr SMITH_HOST_DEVICE auto type(const tuple< T... > &values)
a function intended to be used for extracting the ith type from a tuple.
Definition: tuple.hpp:376
SMITH_HOST_DEVICE tuple< vec3, mat3 > eig_symm(const mat3 &A)
SMITH_HOST_DEVICE auto cos(dual< gradient_type > a)
implementation of cosine for dual numbers
Definition: dual.hpp:316
auto sqrt_symm(tensor< T, 3, 3 > A)
Square root of a symmetric matrix.
constexpr SMITH_HOST_DEVICE auto make_dual_helper(zero)
This is an overloaded member function, provided for convenience. It differs from the above function o...
constexpr SMITH_HOST_DEVICE LuFactorization< T, n > factorize_lu(const tensor< T, n, n > &A)
Compute LU factorization of a matrix with partial pivoting.
constexpr SMITH_HOST_DEVICE auto get_gradient(dual< gradient_type > arg)
return the "gradient" part from a dual number type
Definition: dual.hpp:459
constexpr SMITH_HOST_DEVICE int size(const tensor< T, n... > &)
returns the total number of stored values in a tensor
Definition: tensor.hpp:1939
constexpr SMITH_HOST_DEVICE auto transpose(const isotropic_tensor< T, m, m > &I)
return the transpose of an isotropic tensor
constexpr SMITH_HOST_DEVICE auto symmetric_mat3_function_with_derivative(tensor< dual< Gradient >, 3, 3 > A, tensor< double, 3, 3 > f_A, vec3 lambda, mat3 Q, const Function &g)
Helper function for defining the derivative.
auto symmetric_mat3_function(tensor< T, 3, 3 > A, const Function &f, const EigvalSecantFunction &g)
Constructs an isotropic tensor-valued function of a symmetric 3x3 tensor from a scalar function.
int sgn(T val)
Signum, returns sign of input.
SMITH_HOST_DEVICE tuple< T... > make_tuple(const T &... args)
helper function for combining a list of values into a tuple
Definition: tuple.hpp:266
constexpr SMITH_HOST_DEVICE auto dev(const tensor< T, n, n > &A)
Calculates the deviator of a matrix (rank-2 tensor)
Definition: tensor.hpp:1200
SMITH_HOST_DEVICE auto pow(dual< gradient_type > a, dual< gradient_type > b)
implementation of a (dual) raised to the b (dual) power
Definition: dual.hpp:405
constexpr SMITH_HOST_DEVICE auto 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:445
SMITH_HOST_DEVICE auto abs(dual< gradient_type > x)
Implementation of absolute value function for dual numbers.
Definition: dual.hpp:219
constexpr SMITH_HOST_DEVICE tensor< T, n, n > diag(const tensor< T, n > &d)
Returns a square diagonal matrix by specifying the diagonal entries.
Definition: tensor.hpp:1231
auto find_root(const function &f, tensor< double, n > x0)
Finds a root of a vector-valued nonlinear function.
constexpr auto make_dual_wrt(const smith::tuple< T... > &args)
take a tuple of values, and promote the nth one to a one-hot dual number of the appropriate type
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:1829
auto exp_symm(tensor< T, 3, 3 > A)
Exponential of a symmetric matrix.
FieldStateWeightedSum operator*(double a, const FieldState &b)
multiply scalar by a FieldState to get a temporary FieldStateWeightedSum which can cast back to a Fie...
constexpr SMITH_HOST_DEVICE auto det(const isotropic_tensor< T, m, m > &I)
compute the determinant of an isotropic tensor
constexpr SMITH_HOST_DEVICE auto inner(const dual< S > &A, const dual< T > &B)
Definition: dual.hpp:281
constexpr SMITH_HOST_DEVICE auto tr(const isotropic_tensor< T, m, m > &I)
calculate the trace of an isotropic tensor
SMITH_HOST_DEVICE auto normalize(const tensor< T, n... > &A)
Normalizes the tensor Each element is divided by the Frobenius norm of the tensor,...
Definition: tensor.hpp:1122
constexpr SMITH_HOST_DEVICE auto dot(const isotropic_tensor< S, m, m > &I, const tensor< T, m, n... > &A)
dot product between an isotropic and (nonisotropic) tensor
SMITH_HOST_DEVICE auto promote_each_to_dual_when(const tensor< T, n > &x)
a function that optionally (decided at compile time) converts a list of values to their dual types
typename one_hot< i, n, T >::type one_hot_t
a tuple type with n entries, all of which are of type smith::zero, except for the i^{th} entry,...
SMITH_HOST_DEVICE tensor< int, 3 > argsort(const tensor< T, 3 > &v)
Find indices that would sort a 3-vector.
constexpr SMITH_HOST_DEVICE auto operator/(const dual< gradient_type > &a, double b)
division of a dual number by a non-dual number
Definition: dual.hpp:129
constexpr SMITH_HOST_DEVICE auto linear_solve(const LuFactorization< S, n > &lu_factors, const tensor< T, n, m... > &b)
Definition: tensor.hpp:1626
constexpr SMITH_HOST_DEVICE auto make_dual(double x)
promote a value to a dual number of the appropriate type
Definition: dual.hpp:441
Representation of an LU factorization.
Definition: tensor.hpp:1547
Settings for solve_scalar_equation.
double rtol
absolute tolerance on absolute value of residual
unsigned int max_iter
maximum allowed number of iterations
double xtol
absolute tolerance on Newton correction
Status and diagnostics of nonlinear equation solvers.
unsigned int iterations
Number of iterations taken.
bool converged
converged Flag indicating whether solver converged to a solution or aborted.
double residual
Final value of residual.
Dual number struct (value plus gradient)
Definition: dual.hpp:28
double value
the actual numerical value
Definition: dual.hpp:29
class for checking if a type is a dual number or not
Definition: dual.hpp:466
class for checking if a type is a tensor of dual numbers or not
static constexpr bool value
whether or not type T is a dual number
checks if a type is zero
Definition: tensor.hpp:150
Arbitrary-rank tensor class.
Definition: tensor.hpp:28
Type that mimics std::tuple.
Definition: tuple.hpp:60
Type that mimics std::tuple.
Definition: tuple.hpp:47
This is a class that mimics most of std::tuple's interface, except that it is usable in CUDA kernels ...
Definition: tuple.hpp:28
A sentinel struct for eliding no-op tensor operations.
Definition: tensor.hpp:122
Implementation of the tensor class used by Functional.
Implements a std::tuple-like object that works in CUDA kernels.