Automatic Differentiation
 
Loading...
Searching...
No Matches
sylvester_solver.hpp
Go to the documentation of this file.
1#ifndef STAN_MATH_REV_FUN_SYLVESTER_SOLVER_HPP
2#define STAN_MATH_REV_FUN_SYLVESTER_SOLVER_HPP
3
9#include <Eigen/Eigenvalues>
10#include <complex>
11#include <type_traits>
12
13namespace stan {
14namespace math {
15
36template <typename MatA, typename MatB, typename MatC,
37 require_all_matrix_t<MatA, MatB, MatC>* = nullptr,
38 require_any_bt_var<MatA, MatB, MatC>* = nullptr>
39inline auto sylvester_solver(MatA&& A, MatB&& B, MatC&& C) {
40 using cdouble = std::complex<double>;
41 using ret_type
42 = Eigen::Matrix<std::complex<var>, Eigen::Dynamic, Eigen::Dynamic>;
43
44 check_square("sylvester_solver", "A", A);
45 check_square("sylvester_solver", "B", B);
46 check_size_match("sylvester_solver", "rows of A", A.rows(), "rows of C",
47 C.rows());
48 check_size_match("sylvester_solver", "rows of B", B.rows(), "cols of C",
49 C.cols());
50 if (unlikely(A.rows() == 0 || B.rows() == 0)) {
51 return ret_type(A.rows(), B.rows());
52 }
53
54 auto arena_A = to_arena(std::forward<MatA>(A));
55 auto arena_B = to_arena(std::forward<MatB>(B));
56 auto arena_C = to_arena(std::forward<MatC>(C));
57
58 const Eigen::Index m = B.rows();
59
60 // ---- forward pass: identical to prim, on values ----
61 Eigen::ComplexSchur<plain_type_t<decltype(value_of(arena_A))>> schur_A(
62 value_of(arena_A));
63 auto arena_U = to_arena(schur_A.matrixU());
64 auto arena_S = to_arena(schur_A.matrixT());
65
66 Eigen::ComplexSchur<plain_type_t<decltype(value_of(arena_B))>> schur_B(
67 value_of(arena_B));
68 auto arena_V = to_arena(schur_B.matrixU());
69 auto arena_T = to_arena(schur_B.matrixT());
70
71 // Y holds D = U^H C V initially, overwritten column by column
72 auto Y = (arena_U.adjoint() * value_of(arena_C) * arena_V).eval();
73 plain_type_t<decltype(arena_S)> Sshift = arena_S;
74 for (Eigen::Index i = 0; i < m; ++i) {
75 Y.col(i).noalias() -= Y.leftCols(i) * arena_T.col(i).head(i);
76 Sshift.diagonal() = arena_S.diagonal().array() + arena_T(i, i);
77 Sshift.template triangularView<Eigen::Upper>().solveInPlace(Y.col(i));
78 }
79 arena_t<Eigen::MatrixXcd> arena_X = arena_U * Y * arena_V.adjoint();
80
81 auto res = to_arena(arena_X.cast<std::complex<var>>());
82
83 // ---- reverse pass ----
84 reverse_pass_callback([arena_A, arena_B, arena_C, arena_U, arena_S, arena_V,
85 arena_T, arena_X, res]() mutable {
86 const Eigen::Index rows = arena_S.rows();
87 const Eigen::Index cols = arena_T.rows();
88
89 // G = adj(Re X) + i * adj(Im X)
90 Eigen::MatrixXcd G(rows, cols);
91 for (Eigen::Index c = 0; c < cols; ++c) {
92 for (Eigen::Index r = 0; r < rows; ++r) {
93 G(r, c) = cdouble(res(r, c).real().adj(), res(r, c).imag().adj());
94 }
95 }
96
97 // W holds E = U^H G V initially; solve S^H W + W T^H = E,
98 // overwritten column by column from last to first
99 auto W = (arena_U.adjoint() * G * arena_V).eval();
100 auto Sshift_r = arena_S.eval();
101 for (Eigen::Index j = cols - 1; j >= 0; --j) {
102 const Eigen::Index k = cols - 1 - j; // already-solved columns
103 W.col(j).noalias() -= W.rightCols(k) * arena_T.row(j).tail(k).adjoint();
104 Sshift_r.diagonal() = arena_S.diagonal().array() + arena_T(j, j);
105 Sshift_r.template triangularView<Eigen::Upper>().adjoint().solveInPlace(
106 W.col(j));
107 }
108 const auto Z = (arena_U * W * arena_V.adjoint()).eval();
109
110 // Re/Im of the gradient go to the adjoints of Re/Im of the input;
111 // real inputs only receive Re(grad)
112 auto accumulate = [](auto& in, const Eigen::MatrixXcd& g) {
113 using in_t = std::decay_t<decltype(in)>;
114 if constexpr (is_complex<value_type_t<in_t>>::value) {
115 const Eigen::Index rows = g.rows();
116 const Eigen::Index cols = g.cols();
117 for (Eigen::Index c = 0; c < cols; ++c) {
118 for (Eigen::Index r = 0; r < rows; ++r) {
119 in(r, c).real().adj() += g(r, c).real();
120 in(r, c).imag().adj() += g(r, c).imag();
121 }
122 }
123 } else {
124 in.adj() += g.real();
125 }
126 };
127
128 if constexpr (is_var<base_type_t<MatA>>::value) {
129 accumulate(arena_A, -(Z * arena_X.adjoint()));
130 }
131 if constexpr (is_var<base_type_t<MatB>>::value) {
132 accumulate(arena_B, -(arena_X.adjoint() * Z));
133 }
134 if constexpr (is_var<base_type_t<MatC>>::value) {
135 accumulate(arena_C, Z);
136 }
137 });
138 return ret_type(res);
139}
140
141} // namespace math
142} // namespace stan
143#endif
#define unlikely(x)
int64_t cols(const T_x &x)
Returns the number of columns in the specified kernel generator expression.
Definition cols.hpp:21
int64_t rows(const T_x &x)
Returns the number of rows in the specified kernel generator expression.
Definition rows.hpp:22
void check_square(const char *function, const char *name, const T_y &y)
Check if the specified matrix is square.
T real(const std::complex< T > &z)
Return the real component of the complex argument.
Definition real.hpp:18
auto sylvester_solver(const MatA &A, const MatB &B, const MatC &C)
Solve the Sylvester equation A X + X B = C for X, forward mode.
void reverse_pass_callback(F &&functor)
Puts a callback on the autodiff stack to be called in reverse pass.
T eval(T &&arg)
Inputs which have a plain_type equal to the own time are forwarded unmodified (for Eigen expressions ...
Definition eval.hpp:20
T value_of(const fvar< T > &v)
Return the value of the specified variable.
Definition value_of.hpp:18
T imag(const std::complex< T > &z)
Return the imaginary component of the complex argument.
Definition imag.hpp:18
arena_t< T > to_arena(const T &a)
Converts given argument into a type that either has any dynamic allocation on AD stack or schedules i...
Definition to_arena.hpp:25
void check_size_match(const char *function, const char *name_i, T_size1 i, const char *name_j, T_size2 j)
Check if the provided sizes match.
typename plain_type< std::decay_t< T > >::type plain_type_t
The lgamma implementation in stan-math is based on either the reentrant safe lgamma_r implementation ...