Repository navigation
eigendecompose_sym, eigenvalues_sym and eigenvectors_sym do not compile for complex matrices of fvar<var> or fvar<fvar<double>> #3477
Description
Context: At #3470 @WardBrian remarked that we don't have full coverage, so tests not failing is not a bulletproof evidence that it's safe to merge. I was trying to kind of dig into how a more solid evidence could be constructed and while working on that, I ran into this issue that is apparently a fail with no test coverage.
The following notes are claude generated.
Description
The prim versions of the three functions call check_symmetric on their argument (eigendecompose_sym.hpp:32, eigenvalues_sym.hpp:29, eigenvectors_sym.hpp:20). Complex matrices of forward-mode scalars reach these prim versions. check_symmetric compares the two off-diagonal entries with value_of, which removes only one autodiff layer (stan/math/prim/err/check_symmetric.hpp:45):
if (!(fabs(value_of(y_ref(m, n)) - value_of(y_ref(n, m)))
<= CONSTRAINT_TOLERANCE)) {For std::complex<fvar<var>> the difference is still std::complex<var>, so fabs resolves to libstdc++'s extension std::fabs(const std::complex<T>&) (<complex>:2430 in GCC 14). That calls the generic std::abs, which reads __z.__rep() (<complex>:892), a member of the primary template only. Stan's specializations std::complex<var> and std::complex<fvar<T>> do not have it, so compilation fails. std::complex<fvar<fvar<double>>> fails the same way, because its difference is std::complex<fvar<double>>. std::complex<var> and std::complex<fvar<double>> compile, because value_of reduces them to std::complex<double>.
How Stan programs reach it. eigendecompose_sym(complex_matrix), eigenvalues_sym(complex_matrix) and eigenvectors_sym(complex_matrix) applied to a parameter-dependent matrix compile with the usual flags. They do not compile with STAN_MODEL_FVAR_VAR, which also instantiates log_prob for fvar<var>. BridgeStan sets that macro in its BRIDGESTAN_AD_HESSIAN build (Makefile:40), so such a model cannot be built there.
The mix tests of the three functions use only real matrices, which is why no test fails.
A small, separate defect is in the same place: eigenvectors_sym passes "eigenvalues_sym" as the function name to check_symmetric (eigenvectors_sym.hpp:20), so its error messages name the wrong function.
Example
#include <stan/math/mix.hpp>
int main() {
using stan::math::fvar;
using stan::math::var;
Eigen::Matrix<std::complex<fvar<var>>, -1, -1> m(2, 2);
auto res = stan::math::eigendecompose_sym(m);
}It was compiled from the repository root at 5792530, in WSL2 (Debian 13):
g++ -std=c++17 -D_REENTRANT -DBOOST_DISABLE_ASSERTS -fsyntax-only -I . -isystem lib/eigen_5.0.1 -isystem lib/boost_1.87.0 -isystem lib/sundials_6.1.1/include -isystem lib/tbb_2020.3/include eig.cpp
g++ 14.2.0 reports:
/usr/include/c++/14/complex:892:61: error: ‘const class std::complex<stan::math::var_value<double> >’ has no member named ‘__rep’
clang++ 18.1.8 reports the same error, with this instantiation chain (directory prefixes removed):
c++/14/complex:892:61: error: no member named '__rep' in 'std::complex<stan::math::var>'
c++/14/complex:2430:19: note: in instantiation of function template specialization 'std::abs<stan::math::var_value<double>>' requested here
stan/math/prim/err/check_symmetric.hpp:45:13: note: in instantiation of function template specialization 'std::fabs<stan::math::var_value<double>>' requested here
stan/math/prim/fun/eigendecompose_sym.hpp:32:3: note: in instantiation of function template specialization 'stan::math::check_symmetric<Eigen::Matrix<std::complex<stan::math::fvar<stan::math::var_value<double>>>, -1, -1>, nullptr>' requested here
eigenvalues_sym and eigenvectors_sym fail the same way with fvar<var>, and eigendecompose_sym also with fvar<fvar<double>>.
From Stan, with stanc3 793b15d:
parameters {
real x;
}
model {
complex_matrix[2, 2] m = to_complex([[x, 0], [0, x]], 0);
complex_vector[2] v = eigenvalues_sym(m);
}The generated C++ compiles with the stan headers at stan-dev/stan@849cec8da. With -DSTAN_MODEL_FVAR_VAR it fails with the same __rep error.
Expected Output
All three functions compile and run for complex matrices of every autodiff type, like they do for real matrices.
Comparing with value_of_rec, which is meant for exactly such error checks, fixes it:
if (!(fabs(value_of_rec(y_ref(m, n)) - value_of_rec(y_ref(n, m)))
<= CONSTRAINT_TOLERANCE)) {with #include <stan/math/prim/fun/value_of_rec.hpp> in place of value_of.hpp. With that change the example compiles with g++ 14.2.0 for all three functions with fvar<var>, and for eigendecompose_sym with fvar<fvar<double>>. A complex input in the three mix tests would cover it.
Current Version:
v5.4.0 (develop at 5792530)
Activity
WardBrian commentedon Oct 23, 2026
value_of_rec does appear to be the correct patch here
jachymb commentedon Oct 24, 2026
value_of_recfixes the compilation crash, but I found out there's a deeper issue: the complex matrices are treated as symmetric where they should be treated as Hermitian. So fixing it withvalue_of_recwould actually produce buggy behavior when it compiles.I tried to get it to compile on #3484 with just the
value_of_rec, it technically works, but then I changed my mind of actually going through with that because of this problem.jachymb commentedon Oct 24, 2026
So maybe using the
*_symmethods shouldn't even be allowed for complex valued matrices as that's confusing terminologically. There are some use cases for complex-symmetric (non-hermitian) matrices, but specialized eigendecompostition isn't one (afaik). Instead, we could have*_hermitianfunctions that would mirror the*_symlogic.Thoughts?