Cholesky: Keep blocked complex LLT inverse diagonals exactly real

libeigen/eigen!3031

Closes #3149

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/Eigen/src/Cholesky/LLT.h b/Eigen/src/Cholesky/LLT.h
index 65545b3..64340d4 100644
--- a/Eigen/src/Cholesky/LLT.h
+++ b/Eigen/src/Cholesky/LLT.h
@@ -636,8 +636,8 @@
       llt.solveInPlace(dst);
     }
     // Mirror; (i, j) reads (j, i), which lies in the computed triangle and is never written here, so
-    // the aliasing is benign. The computed diagonal is exactly real (a squaredNorm above the
-    // threshold, a real scalar below it), so the result is exactly self-adjoint.
+    // the aliasing is benign. LAUUM makes the computed diagonal exactly real, as does the real-scalar
+    // solve below the threshold, so the result is exactly self-adjoint.
     dst.template triangularView<kMirrorMode>() = dst.adjoint();
   }
 };
diff --git a/Eigen/src/Core/TriangularInPlace.h b/Eigen/src/Core/TriangularInPlace.h
index a5e28d7..69499ba 100644
--- a/Eigen/src/Core/TriangularInPlace.h
+++ b/Eigen/src/Core/TriangularInPlace.h
@@ -176,7 +176,13 @@
       if (rs > 0) B.noalias() += mat.block(k + bs, k, rs, bs).adjoint() * mat.block(k + bs, 0, rs, k);
     }
     triangular_adjoint_square_unblocked(L11);
-    if (rs > 0) L11.template selfadjointView<Lower>().rankUpdate(mat.block(k + bs, k, rs, bs).adjoint());
+    if (rs > 0) {
+      L11.template selfadjointView<Lower>().rankUpdate(mat.block(k + bs, k, rs, bs).adjoint());
+      // Fused complex products can leave a rounding residual in the imaginary diagonal.
+      EIGEN_IF_CONSTEXPR (NumTraits<typename MatrixType::Scalar>::IsComplex) {
+        L11.diagonal() = L11.diagonal().real().template cast<typename MatrixType::Scalar>();
+      }
+    }
   }
 }
 
diff --git a/test/triangular_in_place.cpp b/test/triangular_in_place.cpp
index 1b06ea5..e1e510b 100644
--- a/test/triangular_in_place.cpp
+++ b/test/triangular_in_place.cpp
@@ -145,6 +145,7 @@
   const MatrixType t = ill_conditioned_triangular<MatrixType>(n);
   MatrixType a = t;
   internal::triangular_adjoint_square_in_place<UpLo>(a);
+  VERIFY((a.diagonal().imag().array() == RealScalar(0)).all());
 
   const DenseType td = t.template triangularView<UpLo>().toDenseMatrix();
   const DenseType expected = UpLo == Lower ? DenseType(td.adjoint() * td) : DenseType(td * td.adjoint());
@@ -180,6 +181,7 @@
       CALL_SUBTEST_5(triangular_inverse_on_strided_map<MatrixXd>(n));
       CALL_SUBTEST_7(adjoint_square_both_triangles<MatrixXd>(n));
       CALL_SUBTEST_8(adjoint_square_both_triangles<MatrixXcd>(n));
+      CALL_SUBTEST_8((adjoint_square_both_triangles<Matrix<std::complex<double>, Dynamic, Dynamic, RowMajor>>(n)));
       CALL_SUBTEST_9(adjoint_square_both_triangles<MatrixXf>(n));
       CALL_SUBTEST_10((adjoint_square_both_triangles<Matrix<double, Dynamic, Dynamic, RowMajor>>(n)));
     }