From a53f5e443341eaf6e8809cc30041128a50749583 Mon Sep 17 00:00:00 2001
From: Tobias Leibner <tobias.leibner@uni-muenster.de>
Date: Wed, 13 Dec 2017 13:38:52 +0100
Subject: [PATCH] [eigen-solver.internal.base] try to compute real eigenvectors
 from complex ones

---
 dune/xt/la/eigen-solver/internal/base.hh | 117 ++++++++++++++++++++---
 1 file changed, 104 insertions(+), 13 deletions(-)

diff --git a/dune/xt/la/eigen-solver/internal/base.hh b/dune/xt/la/eigen-solver/internal/base.hh
index a41a1e107..23767b7b7 100644
--- a/dune/xt/la/eigen-solver/internal/base.hh
+++ b/dune/xt/la/eigen-solver/internal/base.hh
@@ -441,22 +441,113 @@ protected:
       const size_t rows = CM::rows(*self.eigenvectors_);
       const size_t cols = CM::cols(*self.eigenvectors_);
       self.real_eigenvectors_ = std::make_unique<RealMatrixType>(RM::create(rows, cols));
-      for (size_t ii = 0; ii < rows; ++ii)
+      bool is_complex = false;
+      for (size_t ii = 0; ii < rows; ++ii) {
         for (size_t jj = 0; jj < cols; ++jj) {
           const auto complex_value = CM::get_entry(*self.eigenvectors_, ii, jj);
-          if (std::abs(complex_value.imag()) > tolerance)
-            DUNE_THROW(Exceptions::eigen_solver_failed_bc_eigenvectors_are_not_real_as_requested,
-                       "These were the given options:\n\n"
-                           << self.options_
-                           << "\n\nThis was the given matrix: "
-                           << std::setprecision(17)
-                           << self.matrix_
-                           << "\nThese are the computed eigenvectors:\n\n"
-                           << std::setprecision(17)
-                           << *self.eigenvectors_);
+          if (std::abs(complex_value.imag()) > tolerance) {
+            is_complex = true;
+            ii = rows;
+            break;
+          }
           RM::set_entry(*self.real_eigenvectors_, ii, jj, complex_value.real());
-        }
-    }
+        } // jj
+      } // ii
+
+      if (is_complex) {
+        try {
+          // try to get real eigenvectors from the complex ones. If both the matrix and the eigenvalues are real, the
+          // eigenvectors can also be chosen real. If there is a imaginary eigenvector, both real and imaginary part are
+          // eigenvectors (if non-zero) to the same eigenvalue. So to get real eigenvectors, sort the eigenvectors by
+          // eigenvalues and, separately for each eigenvalue, perform a Gram-Schmidt process with all real and imaginary
+          // parts of the eigenvectors
+          auto eigenvalues = self.real_eigenvalues();
+
+          // form groups of equal eigenvalues
+          struct Cmp
+          {
+            bool operator()(const RealType& a, const RealType& b) const
+            {
+              return XT::Common::FloatCmp::lt(a, b);
+            }
+          };
+          std::vector<std::vector<size_t>> eigenvalue_groups;
+          std::vector<size_t> eigenvalue_multiplicity;
+          std::set<RealType, Cmp> eigenvalues_done;
+          for (size_t jj = 0; jj < rows; ++jj) {
+            const auto curr_eigenvalue = eigenvalues[jj];
+            if (!eigenvalues_done.count(curr_eigenvalue)) {
+              std::vector<size_t> curr_group;
+              curr_group.push_back(jj);
+              eigenvalue_multiplicity.push_back(1);
+              for (size_t kk = jj + 1; kk < rows; ++kk) {
+                if (XT::Common::FloatCmp::eq(curr_eigenvalue, eigenvalues[kk])) {
+                  curr_group.push_back(kk);
+                  ++(eigenvalue_multiplicity.back());
+                }
+              } // kk
+              eigenvalue_groups.push_back(curr_group);
+              eigenvalues_done.insert(curr_eigenvalue);
+            }
+          } // jj
+
+          // For each eigenvalue, calculate a orthogonal basis of the n-dim real eigenspace from the 2n real
+          // and imaginary parts of the complex eigenvectors
+          for (size_t kk = 0; kk < eigenvalue_groups.size(); ++kk) {
+            const auto& group = eigenvalue_groups[kk];
+            typedef typename XT::LA::CommonDenseVector<RealType> RealVectorType;
+            std::vector<RealVectorType> input_vectors(2 * eigenvalue_multiplicity[kk], RealVectorType(rows, 0.));
+            size_t index = 0;
+            for (const auto& jj : group) {
+              for (size_t ll = 0; ll < cols; ++ll) {
+                input_vectors[index][ll] = CM::get_entry(*self.eigenvectors_, ll, jj).real();
+                input_vectors[index + 1][ll] = CM::get_entry(*self.eigenvectors_, ll, jj).imag();
+              }
+              index += 2;
+            } // jj
+
+            // orthonormalize
+            for (size_t ii = 1; ii < input_vectors.size(); ++ii) {
+              auto& v_i = input_vectors[ii];
+              for (size_t jj = 0; jj < ii; ++jj) {
+                const auto& v_j = input_vectors[jj];
+                const auto vj_vj = v_j.dot(v_j);
+                if (XT::Common::FloatCmp::eq(vj_vj, 0.))
+                  continue;
+                const auto vj_vi = v_j.dot(v_i);
+                for (size_t rr = 0; rr < rows; ++rr)
+                  v_i[rr] -= vj_vi / vj_vj * v_j[rr];
+              } // jj
+              auto l2_norm = std::accumulate(v_i.begin(), v_i.end(), 0.);
+              if (XT::Common::FloatCmp::ne(l2_norm, 0.))
+                v_i *= 1. / std::sqrt(l2_norm);
+            } // ii
+            // copy eigenvectors back to eigenvectors matrix
+            index = 0;
+            for (size_t ii = 0; ii < input_vectors.size(); ++ii) {
+              if (XT::Common::FloatCmp::ne(input_vectors[ii], RealVectorType(rows, 0.))) {
+                index++;
+                if (index >= eigenvalue_multiplicity[ii])
+                  DUNE_THROW(Exceptions::eigen_solver_failed_bc_eigenvectors_are_not_real_as_requested,
+                             "Eigenvectors are complex and calculating real eigenvectors failed!");
+                for (size_t rr = 0; rr < rows; ++rr)
+                  RM::set_entry(*self.real_eigenvectors_, rr, group[index], input_vectors[ii].get_entry(rr));
+              } // if (input_vectors[ii] != 0)
+            } // ii
+          } // kk
+        } catch (const Dune::Exception& e) {
+          DUNE_THROW(Exceptions::eigen_solver_failed_bc_eigenvectors_are_not_real_as_requested,
+                     "These were the given options:\n\n"
+                         << self.options_
+                         << "\n\nThis was the given matrix: "
+                         << std::setprecision(17)
+                         << self.matrix_
+                         << "\nThese are the computed eigenvectors:\n\n"
+                         << std::setprecision(17)
+                         << *self.eigenvectors_);
+        } // try/catch
+      } // if(is_complex)
+    } // static void compute(...)
   }; // real_eigenvectors_helper<true, ...>
 
   template <class T>
-- 
GitLab