SelfAdjointView: add l1Norm() and use it in LLT / LDLT

libeigen/eigen!2456

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/Eigen/src/Cholesky/LDLT.h b/Eigen/src/Cholesky/LDLT.h
index 45912a2..63aa5bd 100644
--- a/Eigen/src/Cholesky/LDLT.h
+++ b/Eigen/src/Cholesky/LDLT.h
@@ -489,19 +489,8 @@
 
   m_matrix = a.derived();
 
-  // Compute matrix L1 norm = max abs column sum.
-  m_l1_norm = RealScalar(0);
-  // TODO: move this code to SelfAdjointView
-  for (Index col = 0; col < size; ++col) {
-    RealScalar abs_col_sum;
-    if (UpLo_ == Lower)
-      abs_col_sum =
-          m_matrix.col(col).tail(size - col).template lpNorm<1>() + m_matrix.row(col).head(col).template lpNorm<1>();
-    else
-      abs_col_sum =
-          m_matrix.col(col).head(col).template lpNorm<1>() + m_matrix.row(col).tail(size - col).template lpNorm<1>();
-    if (abs_col_sum > m_l1_norm) m_l1_norm = abs_col_sum;
-  }
+  // Compute matrix L1 norm = max abs column sum over the implicit self-adjoint matrix.
+  m_l1_norm = m_matrix.template selfadjointView<UpLo_>().l1Norm();
 
   m_transpositions.resize(size);
   m_isInitialized = false;
diff --git a/Eigen/src/Cholesky/LLT.h b/Eigen/src/Cholesky/LLT.h
index 7066cd0..9bffeae 100644
--- a/Eigen/src/Cholesky/LLT.h
+++ b/Eigen/src/Cholesky/LLT.h
@@ -404,19 +404,8 @@
   m_matrix.resize(size, size);
   if (!internal::is_same_dense(m_matrix, a.derived())) m_matrix = a.derived();
 
-  // Compute matrix L1 norm = max abs column sum.
-  m_l1_norm = RealScalar(0);
-  // TODO: move this code to SelfAdjointView
-  for (Index col = 0; col < size; ++col) {
-    RealScalar abs_col_sum;
-    if (UpLo_ == Lower)
-      abs_col_sum =
-          m_matrix.col(col).tail(size - col).template lpNorm<1>() + m_matrix.row(col).head(col).template lpNorm<1>();
-    else
-      abs_col_sum =
-          m_matrix.col(col).head(col).template lpNorm<1>() + m_matrix.row(col).tail(size - col).template lpNorm<1>();
-    if (abs_col_sum > m_l1_norm) m_l1_norm = abs_col_sum;
-  }
+  // Compute matrix L1 norm = max abs column sum over the implicit self-adjoint matrix.
+  m_l1_norm = m_matrix.template selfadjointView<UpLo_>().l1Norm();
 
   m_isInitialized = true;
   bool ok = Traits::inplace_decomposition(m_matrix);
diff --git a/Eigen/src/Core/SelfAdjointView.h b/Eigen/src/Core/SelfAdjointView.h
index ff450eb..59731b8 100644
--- a/Eigen/src/Core/SelfAdjointView.h
+++ b/Eigen/src/Core/SelfAdjointView.h
@@ -223,6 +223,28 @@
     return typename MatrixType::ConstDiagonalReturnType(m_matrix);
   }
 
+  /** \returns the matrix 1-norm (maximum absolute column sum) of the implicit
+   * full self-adjoint matrix, reading only the stored triangle. For Hermitian
+   * (complex) scalars the unstored entries are conjugates of stored ones, and
+   * since |conj(x)| = |x| the result matches the L1 norm of the full matrix.
+   */
+  EIGEN_DEVICE_FUNC typename NumTraits<Scalar>::Real l1Norm() const {
+    typedef typename NumTraits<Scalar>::Real RealScalar_;
+    RealScalar_ norm = RealScalar_(0);
+    const Index n = m_matrix.rows();
+    for (Index col = 0; col < n; ++col) {
+      RealScalar_ abs_col_sum;
+      if (UpLo == Lower)
+        abs_col_sum =
+            m_matrix.col(col).tail(n - col).template lpNorm<1>() + m_matrix.row(col).head(col).template lpNorm<1>();
+      else
+        abs_col_sum =
+            m_matrix.col(col).head(col).template lpNorm<1>() + m_matrix.row(col).tail(n - col).template lpNorm<1>();
+      if (abs_col_sum > norm) norm = abs_col_sum;
+    }
+    return norm;
+  }
+
   /////////// Cholesky module ///////////
 
   LLT<PlainObject, UpLo> llt() const;
diff --git a/test/selfadjoint.cpp b/test/selfadjoint.cpp
index 65d4e79..87d3152 100644
--- a/test/selfadjoint.cpp
+++ b/test/selfadjoint.cpp
@@ -54,6 +54,24 @@
   VERIFY_IS_APPROX(m4, MatrixType((s * m1).template selfadjointView<Lower>()));
   m4 = m1.template selfadjointView<Lower>() * s;
   VERIFY_IS_APPROX(m4, MatrixType((m1 * s).template selfadjointView<Lower>()));
+
+  // l1Norm: reads only the stored triangle but must agree with the L1 norm of
+  // the materialized full matrix. Upper and Lower views of the same (stored-
+  // full) self-adjoint matrix must return the same value; complex scalars
+  // behave identically since |conj(x)| = |x|.
+  typedef typename NumTraits<Scalar>::Real RealScalar;
+  m3 = m1.template selfadjointView<Upper>();  // m3 is now fully self-adjoint
+  RealScalar ref_l1 = m3.cwiseAbs().colwise().sum().maxCoeff();
+  VERIFY_IS_APPROX(m3.template selfadjointView<Upper>().l1Norm(), ref_l1);
+  VERIFY_IS_APPROX(m3.template selfadjointView<Lower>().l1Norm(), ref_l1);
+  // Either triangle alone still gives the correct L1 norm even if the other
+  // half is zero (the view conjure it back via symmetry).
+  MatrixType upperOnly = MatrixType::Zero(rows, cols);
+  upperOnly.template triangularView<Upper>() = m3;
+  VERIFY_IS_APPROX(upperOnly.template selfadjointView<Upper>().l1Norm(), ref_l1);
+  MatrixType lowerOnly = MatrixType::Zero(rows, cols);
+  lowerOnly.template triangularView<Lower>() = m3;
+  VERIFY_IS_APPROX(lowerOnly.template selfadjointView<Lower>().l1Norm(), ref_l1);
 }
 
 void bug_159() {