From c5a804150bf1e72164d10c452b5dfd113eb9a567 Mon Sep 17 00:00:00 2001
From: podlesny <podlesny@zedat.fu-berlin.de>
Date: Thu, 4 Feb 2021 12:33:03 +0100
Subject: [PATCH] robust implementation of eigenvalue computation, prevents
 complex eigenvalues due to round-off errors

---
 .../spatial-solving/tnnmg/functional.hh       | 13 ++--
 dune/tectonic/utils/CMakeLists.txt            |  2 +
 dune/tectonic/utils/maxeigenvalue.hh          | 60 +++++++++++++++++++
 3 files changed, 71 insertions(+), 4 deletions(-)
 create mode 100644 dune/tectonic/utils/maxeigenvalue.hh

diff --git a/dune/tectonic/spatial-solving/tnnmg/functional.hh b/dune/tectonic/spatial-solving/tnnmg/functional.hh
index 3e9eab1a..2e76d0aa 100755
--- a/dune/tectonic/spatial-solving/tnnmg/functional.hh
+++ b/dune/tectonic/spatial-solving/tnnmg/functional.hh
@@ -16,6 +16,7 @@
 
 #include <dune/tnnmg/functionals/boxconstrainedquadraticfunctional.hh>
 
+#include "../../utils/maxeigenvalue.hh"
 #include "../../utils/debugutils.hh"
 
 template<class M, class V, class N, class L, class U, class R>
@@ -423,18 +424,19 @@ class Functional : public Dune::TNNMG::BoxConstrainedQuadraticFunctional<M, V, L
   {
      for (size_t i=0; i<this->quadraticPart().N(); ++i) {
        const auto& Aii = this->quadraticPart()[i][i];
+       maxEigenvalues_[i] = maxEigenvalue(Aii);
 
        // due to numerical imprecision, Aii might not be symmetric leading to complex eigenvalues
        // consider Toeplitz decomposition of Aii and take only symmetric part
 
-       auto symBlock = Aii;
+       /*auto symBlock = Aii;
 
-       /*for (size_t j=0; j<symBlock.N(); j++) {
+       for (size_t j=0; j<symBlock.N(); j++) {
            for (size_t k=0; k<symBlock.M(); k++) {
                if (symBlock[j][k]/symBlock[j][j] < 1e-14)
                 symBlock[j][k] = 0;
            }
-       }*/
+       }
 
        try {
        typename Vector::block_type eigenvalues;
@@ -442,8 +444,11 @@ class Functional : public Dune::TNNMG::BoxConstrainedQuadraticFunctional<M, V, L
        maxEigenvalues_[i] =
            *std::max_element(std::begin(eigenvalues), std::end(eigenvalues));
        } catch (Dune::Exception &e) {
+            print(symBlock, "symBlock");
+            typename Vector::block_type eigenvalues;
+            Dune::FMatrixHelp::eigenValues(symBlock, eigenvalues);
             std::cout << "complex eig in dof: " << i << std::endl;
-       }
+       } */
      }
   }
 
diff --git a/dune/tectonic/utils/CMakeLists.txt b/dune/tectonic/utils/CMakeLists.txt
index c5498570..9d8acab4 100644
--- a/dune/tectonic/utils/CMakeLists.txt
+++ b/dune/tectonic/utils/CMakeLists.txt
@@ -4,6 +4,7 @@ add_custom_target(tectonic_dune_utils SOURCES
   diameter.hh
   geocoordinate.hh
   index-in-sorted-range.hh
+  maxeigenvalue.hh
   tobool.hh
 )
 
@@ -14,5 +15,6 @@ install(FILES
   diameter.hh
   geocoordinate.hh
   index-in-sorted-range.hh
+  maxeigenvalue.hh
   tobool.hh
 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dune/tectonic)
diff --git a/dune/tectonic/utils/maxeigenvalue.hh b/dune/tectonic/utils/maxeigenvalue.hh
new file mode 100644
index 00000000..47cbcb5a
--- /dev/null
+++ b/dune/tectonic/utils/maxeigenvalue.hh
@@ -0,0 +1,60 @@
+// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+// vi: set et ts=4 sw=2 sts=2:
+#ifndef DUNE_TECTONIC_UTILS_MAXEIGENVALUES_HH
+#define DUNE_TECTONIC_UTILS_MAXEIGENVALUES_HH
+
+/** \file
+ * \brief max eigenvalue computations for the FieldMatrix class
+ */
+
+#include <iostream>
+#include <cmath>
+
+#include <dune/common/exceptions.hh>
+#include <dune/common/fvector.hh>
+#include <dune/common/fmatrix.hh>
+#include <dune/common/fmatrixev.hh>
+
+template <typename K>
+static K maxEigenvalue(const Dune::FieldMatrix<K, 1, 1>& matrix) {
+    return matrix[0][0];
+}
+
+/** \brief calculates the eigenvalues of a symmetric field matrix
+    \param[in]  matrix matrix eigenvalues are calculated for
+    \param[out] max eigenvalue
+*/
+template <typename K>
+static K maxEigenvalue(const Dune::FieldMatrix<K, 2, 2>& matrix) {
+    const K p = 0.5 * (matrix[0][0] + matrix [1][1]);
+    const K p2 = p - matrix[1][1];
+    K q = p2 * p2 + matrix[1][0] * matrix[0][1];
+    if( q < 0 && q > -1e-14 ) q = 0;
+    if (q < 0) {
+        std::cout << matrix << std::endl;
+        // Complex eigenvalues are either caused by non-symmetric matrices or by round-off errors
+        DUNE_THROW(Dune::MathError, "Complex eigenvalue detected (which this implementation cannot handle).");
+    }
+
+    // get square root
+    q = std::sqrt(q);
+
+    return p + q;
+}
+
+/** \brief Calculates the eigenvalues of a symmetric 3x3 field matrix
+    \param[in]  matrix eigenvalues are calculated for
+    \param[out] max eigenvalue
+
+    \note If the input matrix is not symmetric the behavior of this method is undefined.
+ */
+template <typename K>
+static K maxEigenvalue(const Dune::FieldMatrix<K, 3, 3>& matrix) {
+    Dune::FieldVector<K, 3> eigenvalues;
+    Dune::FMatrixHelp::eigenValues(matrix, eigenvalues);
+    return eigenvalues[0];
+}
+
+  /** @} end documentation */
+
+#endif
-- 
GitLab