From 3fda7d9a81a44708fa7ba928c3933bb8ae26c69c Mon Sep 17 00:00:00 2001
From: Tobias Leibner <tobias.leibner@googlemail.com>
Date: Fri, 1 Feb 2019 15:04:50 +0100
Subject: [PATCH] [solver] add a saddle point solver (WIP)

---
 dune/xt/la/container/istl.hh              |   2 +
 dune/xt/la/solver/istl/amg.hh             |  37 +---
 dune/xt/la/solver/istl/preconditioners.hh |  70 ++++++++
 dune/xt/la/solver/istl/saddlepoint.hh     | 206 ++++++++++++++++++++++
 dune/xt/la/solver/istl/schurcomplement.hh | 184 +++++++++++++++++++
 5 files changed, 464 insertions(+), 35 deletions(-)
 create mode 100644 dune/xt/la/solver/istl/preconditioners.hh
 create mode 100644 dune/xt/la/solver/istl/saddlepoint.hh
 create mode 100644 dune/xt/la/solver/istl/schurcomplement.hh

diff --git a/dune/xt/la/container/istl.hh b/dune/xt/la/container/istl.hh
index ed7f4f2a0..884671707 100644
--- a/dune/xt/la/container/istl.hh
+++ b/dune/xt/la/container/istl.hh
@@ -101,6 +101,8 @@ public:
   using typename ProvidesDataAccess<Traits>::DataType;
   // needed to fix gcc compilation error due to ambiguous lookup of derived type
   using derived_type = typename Traits::derived_type;
+  // for dune-istl's LinearOperator
+  using field_type = ScalarType;
 
 private:
   using MutexesType = typename Traits::MutexesType;
diff --git a/dune/xt/la/solver/istl/amg.hh b/dune/xt/la/solver/istl/amg.hh
index fe81068cd..ceded1782 100644
--- a/dune/xt/la/solver/istl/amg.hh
+++ b/dune/xt/la/solver/istl/amg.hh
@@ -28,47 +28,14 @@
 #include <dune/xt/common/parallel/helper.hh>
 #include <dune/xt/la/container/istl.hh>
 
+#include "preconditioners.hh"
+
 namespace Dune {
 namespace XT {
 namespace LA {
 
 #if HAVE_DUNE_ISTL
 
-template <class O>
-class IdentityPreconditioner : public Dune::Preconditioner<typename O::domain_type, typename O::range_type>
-{
-public:
-  //! \brief The domain type of the preconditioner.
-  typedef typename O::domain_type domain_type;
-  //! \brief The range type of the preconditioner.
-  typedef typename O::range_type range_type;
-  //! \brief The field type of the preconditioner.
-  typedef typename range_type::field_type field_type;
-  typedef O InverseOperator;
-
-  IdentityPreconditioner(const SolverCategory::Category cat)
-    : category_(cat)
-  {}
-
-  //! Category of the preconditioner (see SolverCategory::Category)
-  virtual SolverCategory::Category category() const override final
-  {
-    return category_;
-  }
-
-  virtual void pre(domain_type&, range_type&) override final {}
-
-  virtual void apply(domain_type& v, const range_type& d) override final
-  {
-    v = d;
-  }
-
-  virtual void post(domain_type&) override final {}
-
-private:
-  SolverCategory::Category category_;
-};
-
 //! the general, parallel case
 template <class S, class CommunicatorType>
 class AmgApplicator
diff --git a/dune/xt/la/solver/istl/preconditioners.hh b/dune/xt/la/solver/istl/preconditioners.hh
new file mode 100644
index 000000000..f35112d4d
--- /dev/null
+++ b/dune/xt/la/solver/istl/preconditioners.hh
@@ -0,0 +1,70 @@
+// This file is part of the dune-xt-la project:
+//   https://github.com/dune-community/dune-xt-la
+// Copyright 2009-2018 dune-xt-la developers and contributors. All rights reserved.
+// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
+//      or  GPL-2.0+ (http://opensource.org/licenses/gpl-license)
+//          with "runtime exception" (http://www.dune-project.org/license.html)
+// Authors:
+//   Barbara Verfürth (2015)
+//   Felix Schindler  (2014 - 2017)
+//   Rene Milk        (2014 - 2016, 2018)
+//   Tobias Leibner   (2014, 2017)
+
+#ifndef DUNE_XT_LA_SOLVER_ISTL_PRECONDITIONERS_HH
+#define DUNE_XT_LA_SOLVER_ISTL_PRECONDITIONERS_HH
+
+#include <type_traits>
+#include <cmath>
+
+#if HAVE_DUNE_ISTL
+#  include <dune/istl/preconditioners.hh>
+#endif // HAVE_DUNE_ISTL
+
+namespace Dune {
+namespace XT {
+namespace LA {
+
+#if HAVE_DUNE_ISTL
+
+template <class O>
+class IdentityPreconditioner : public Dune::Preconditioner<typename O::domain_type, typename O::range_type>
+{
+public:
+  //! \brief The domain type of the preconditioner.
+  typedef typename O::domain_type domain_type;
+  //! \brief The range type of the preconditioner.
+  typedef typename O::range_type range_type;
+  //! \brief The field type of the preconditioner.
+  typedef typename range_type::field_type field_type;
+  typedef O InverseOperator;
+
+  IdentityPreconditioner(const SolverCategory::Category cat)
+    : category_(cat)
+  {}
+
+  //! Category of the preconditioner (see SolverCategory::Category)
+  virtual SolverCategory::Category category() const override final
+  {
+    return category_;
+  }
+
+  virtual void pre(domain_type&, range_type&) override final {}
+
+  virtual void apply(domain_type& v, const range_type& d) override final
+  {
+    v = d;
+  }
+
+  virtual void post(domain_type&) override final {}
+
+private:
+  SolverCategory::Category category_;
+};
+
+#endif // HAVE_DUNE_ISTL
+
+} // namespace LA
+} // namespace XT
+} // namespace Dune
+
+#endif // DUNE_XT_LA_SOLVER_ISTL_PRECONDITIONERS_HH
diff --git a/dune/xt/la/solver/istl/saddlepoint.hh b/dune/xt/la/solver/istl/saddlepoint.hh
new file mode 100644
index 000000000..8f76bf3b8
--- /dev/null
+++ b/dune/xt/la/solver/istl/saddlepoint.hh
@@ -0,0 +1,206 @@
+// This file is part of the dune-xt-la project:
+//   https://github.com/dune-community/dune-xt-la
+// Copyright 2009-2018 dune-xt-la developers and contributors. All rights reserved.
+// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
+//      or  GPL-2.0+ (http://opensource.org/licenses/gpl-license)
+//          with "runtime exception" (http://www.dune-project.org/license.html)
+// Authors:
+//   Barbara Verfürth (2015)
+//   Felix Schindler  (2014 - 2017)
+//   Rene Milk        (2014 - 2016, 2018)
+//   Tobias Leibner   (2014, 2017)
+
+#ifndef DUNE_XT_LA_SOLVER_ISTL_SADDLEPOINT_HH
+#define DUNE_XT_LA_SOLVER_ISTL_SADDLEPOINT_HH
+
+#include "config.h"
+
+#include <type_traits>
+#include <cmath>
+
+#if HAVE_DUNE_ISTL
+#  include <dune/istl/operators.hh>
+#  include <dune/istl/solvers.hh>
+#endif // HAVE_DUNE_ISTL
+
+#include <dune/xt/common/exceptions.hh>
+#include <dune/xt/common/configuration.hh>
+#include <dune/xt/la/container/istl.hh>
+#include <dune/xt/la/solver.hh>
+
+#include "preconditioners.hh"
+#include "schurcomplement.hh"
+
+namespace Dune {
+namespace XT {
+namespace LA {
+
+
+#if HAVE_DUNE_ISTL
+
+
+// Solver for saddle point system (A B1; B2^T C) (u; p) = (f; g) using the Schur complement, i.e., solve (B2^T A^{-1} B1
+// - C) p = B2^T A^{-1} f - g first and then u = A^{-1} (F - B1 p)
+template <class FieldType = double, class CommunicatorType = SequentialCommunication>
+class SaddlePointSolver
+{
+public:
+  using Vector = IstlDenseVector<FieldType>;
+  using Matrix = IstlRowMajorSparseMatrix<FieldType>;
+  using Solver = Solver<Matrix, CommunicatorType>;
+
+  // Matrix and vector dimensions are
+  // A: m x m, B1, B2: m x n, C: n x n, f: m, g: n
+  SaddlePointSolver(const Matrix& A,
+                    const Matrix& B1,
+                    const Matrix& B2,
+                    const Matrix& C,
+                    const Common::Configuration& solver_opts = SolverOptions<Matrix>::options())
+    : schur_complement_(A, B1, B2, C, solver_opts)
+  {}
+
+  static std::vector<std::string> types()
+  {
+    std::vector<std::string> ret{"direct"};
+    ret.insert(ret.begin(), "cg_direct_schurcomplement");
+    return ret;
+  } // ... types()
+
+  static XT::Common::Configuration options(const std::string type = "")
+  {
+    const std::string tp = !type.empty() ? type : types()[0];
+    internal::SolverUtils::check_given(tp, types());
+    Common::Configuration general_opts({"type", "post_check_solves_system", "verbose"}, {tp.c_str(), "1e-5", "0"});
+    Common::Configuration iterative_options({"max_iter", "precision"}, {"10000", "1e-10"});
+    iterative_options += general_opts;
+    if (tp == "direct")
+      return general_opts;
+    else if (tp == "cg_direct_schurcomplement")
+      return iterative_options;
+    else
+      return general_opts;
+  } // ... options(...)
+
+  void apply(const Vector& f, const Vector& g, Vector& u, Vector& p) const
+  {
+    apply(f, g, u, p, types()[0]);
+  }
+
+  void apply(const Vector& f, const Vector& g, Vector& u, Vector& p, const std::string& type) const
+  {
+    apply(f, g, u, p, options(type));
+  }
+
+  int verbosity(const Common::Configuration& opts, const Common::Configuration& default_opts) const
+  {
+    const auto actual_value = opts.get("verbose", default_opts.get<int>("verbose"));
+    return
+#  if HAVE_MPI
+        (communicator_.access().communicator().rank() == 0) ? actual_value : 0;
+#  else
+        actual_value;
+#  endif
+  }
+
+  void apply(const Vector& f, const Vector& g, Vector& u, Vector& p, const Common::Configuration& opts) const
+  {
+    const auto type = opts.get<std::string>("type");
+    if (type == "direct") {
+      // copy matrices to saddle point system matrix
+      // create pattern first
+      const size_t m = f.size();
+      const size_t n = g.size();
+      XT::LA::SparsityPatternDefault system_matrix_pattern(m + n);
+      const auto& A = schur_complement_.A();
+      const auto& B1 = schur_complement_.B1();
+      const auto& B2 = schur_complement_.B2();
+      const auto& C = schur_complement_.C();
+      const auto pattern_A = A.pattern();
+      const auto pattern_B1 = B1.pattern();
+      const auto pattern_B2 = B2.pattern();
+      const auto pattern_C = C.pattern();
+      for (size_t ii = 0; ii < m; ++ii) {
+        for (const auto& jj : pattern_A.inner(ii))
+          system_matrix_pattern.insert(ii, jj);
+        for (const auto& jj : pattern_B1.inner(ii))
+          system_matrix_pattern.insert(ii, m + jj);
+        for (const auto& jj : pattern_B2.inner(ii))
+          system_matrix_pattern.insert(m + jj, ii);
+      } // ii
+      for (size_t ii = 0; ii < n; ++ii)
+        for (const auto& jj : pattern_C.inner(ii))
+          system_matrix_pattern.insert(m + ii, m + jj);
+      system_matrix_pattern.sort();
+
+      // now copy the matrices
+      Matrix system_matrix(m + n, m + n, system_matrix_pattern);
+      for (size_t ii = 0; ii < m; ++ii) {
+        for (const auto& jj : pattern_A.inner(ii))
+          system_matrix.set_entry(ii, jj, A.get_entry(ii, jj));
+        for (const auto& jj : pattern_B1.inner(ii))
+          system_matrix.set_entry(ii, m + jj, B1.get_entry(ii, jj));
+        for (const auto& jj : pattern_B2.inner(ii))
+          system_matrix.set_entry(m + jj, ii, B2.get_entry(ii, jj));
+      } // ii
+      for (size_t ii = 0; ii < n; ++ii)
+        for (const auto& jj : pattern_C.inner(ii))
+          system_matrix.set_entry(m + ii, m + jj, C.get_entry(ii, jj));
+
+      // also copy the rhs
+      Vector system_vector(m + n, 0.), solution_vector(m + n, 0.);
+      for (size_t ii = 0; ii < m; ++ii)
+        system_vector[ii] = f[ii];
+      for (size_t ii = 0; ii < n; ++ii)
+        system_vector[m + ii] = g[ii];
+
+      // solve the system by a direct solver
+      XT::LA::solve(system_matrix, system_vector, solution_vector);
+
+      // copy to result vectors
+      for (size_t ii = 0; ii < m; ++ii)
+        u[ii] = solution_vector[ii];
+      for (size_t ii = 0; ii < n; ++ii)
+        p[ii] = solution_vector[m + ii];
+    } else if (type == "cg_direct_schurcomplement") {
+      // calculate rhs B2^T A^{-1} f - g
+      auto Ainv_f = f;
+      auto rhs_p = g;
+      schur_complement_.A_inv().apply(f, Ainv_f);
+      schur_complement_.B2().mtv(Ainv_f, rhs_p);
+      rhs_p -= g;
+
+      // Solve S p = rhs
+      IdentityPreconditioner<SchurComplementOperator<FieldType, CommunicatorType>> prec(
+          SolverCategory::Category::sequential);
+      auto schur_complement_copy = schur_complement_;
+      Dune::CGSolver<typename Vector::BackendType> outer_solver(schur_complement_copy, prec, 1e-10, 10000, 0, false);
+      InverseOperatorResult res;
+      outer_solver.apply(p.backend(), rhs_p.backend(), res);
+
+      // Now solve u = A^{-1}(f - B1 p)
+      auto rhs_u = f;
+      rhs_u -= schur_complement_.B1() * p;
+      schur_complement_.A_inv().apply(rhs_u, u);
+    }
+  } // ... apply(...)
+
+private:
+  const SchurComplementOperator<FieldType, CommunicatorType> schur_complement_;
+};
+
+#else // HAVE_DUNE_ISTL
+
+template <class FieldType = double, class CommunicatorType = SequentialCommunication>
+class SaddlePointSolver
+{
+  static_assert(Dune::AlwaysFalse<FieldType>::value, "You are missing dune-istl!");
+};
+
+
+#endif // HAVE_DUNE_ISTL
+
+} // namespace LA
+} // namespace XT
+} // namespace Dune
+
+#endif // DUNE_XT_LA_SOLVER_ISTL_SADDLEPOINT_HH
diff --git a/dune/xt/la/solver/istl/schurcomplement.hh b/dune/xt/la/solver/istl/schurcomplement.hh
new file mode 100644
index 000000000..4bdd234c4
--- /dev/null
+++ b/dune/xt/la/solver/istl/schurcomplement.hh
@@ -0,0 +1,184 @@
+// This file is part of the dune-xt-la project:
+//   https://github.com/dune-community/dune-xt-la
+// Copyright 2009-2018 dune-xt-la developers and contributors. All rights reserved.
+// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
+//      or  GPL-2.0+ (http://opensource.org/licenses/gpl-license)
+//          with "runtime exception" (http://www.dune-project.org/license.html)
+// Authors:
+//   Tobias Leibner   (2019)
+
+#ifndef DUNE_XT_LA_SOLVER_ISTL_SCHURCOMPLEMENT_HH
+#define DUNE_XT_LA_SOLVER_ISTL_SCHURCOMPLEMENT_HH
+
+#if HAVE_DUNE_ISTL
+#  include <dune/istl/operators.hh>
+#  include <dune/istl/solvers.hh>
+#endif // HAVE_DUNE_ISTL
+
+#include <dune/xt/common/exceptions.hh>
+#include <dune/xt/common/configuration.hh>
+#include <dune/xt/la/container/istl.hh>
+#include <dune/xt/la/solver.hh>
+
+namespace Dune {
+namespace XT {
+namespace LA {
+
+
+#if HAVE_DUNE_ISTL
+
+
+// For a saddle point matrix (A B1; B2^T C) this models the Schur complement (B2^T A^{-1} B1 - C)
+template <class FieldType = double, class CommunicatorType = SequentialCommunication>
+class SchurComplementOperator
+  : public Dune::LinearOperator<typename IstlDenseVector<FieldType>::BackendType,
+                                typename IstlDenseVector<FieldType>::BackendType>
+{
+  using BaseType = Dune::LinearOperator<typename IstlDenseVector<FieldType>::BackendType,
+                                        typename IstlDenseVector<FieldType>::BackendType>;
+
+public:
+  using Vector = IstlDenseVector<FieldType>;
+  using VectorBackend = typename Vector::BackendType;
+  using Matrix = IstlRowMajorSparseMatrix<FieldType>;
+  using Solver = Solver<Matrix, CommunicatorType>;
+
+  // Matrix dimensions are
+  // A: m x m, B1, B2: m x n, C: n x n
+  SchurComplementOperator(const Matrix& _A,
+                          const Matrix& _B1,
+                          const Matrix& _B2,
+                          const Matrix& _C,
+                          const Common::Configuration& solver_opts = SolverOptions<Matrix>::options())
+    : A_(_A)
+    , A_inv_(make_solver(A_))
+    , B1_(_B1)
+    , B2_(_B2)
+    , C_(_C)
+    , solver_opts_(solver_opts)
+    , m_vec_1_(_A.rows())
+    , m_vec_2_(_A.rows())
+    , n_vec_1_(_C.rows())
+    , n_vec_2_(_C.rows())
+  {}
+
+  SchurComplementOperator(const SchurComplementOperator& other)
+    : A_(other.A_)
+    , A_inv_(make_solver(A_))
+    , B1_(other.B1_)
+    , B2_(other.B2_)
+    , C_(other.C_)
+    , solver_opts_(other.solver_opts_)
+    , m_vec_1_(other.m_vec_1_)
+    , m_vec_2_(other.m_vec_2_)
+    , n_vec_1_(other.n_vec_1_)
+    , n_vec_2_(other.n_vec_2_)
+  {}
+
+  /*! \brief apply operator to x:  \f$ y = S(x) \f$
+        The input vector is consistent and the output must also be
+     consistent on the interior+border partition.
+   */
+  virtual void apply(const VectorBackend& x, VectorBackend& y) const override final
+  {
+    Vector x_la_vector(x);
+    Vector y_la_vector(y);
+    apply(x_la_vector, y_la_vector);
+    y = y_la_vector.backend();
+  }
+
+  virtual void apply(const Vector& x, Vector& y) const
+  {
+    // we want to calculate y = (B2^T A^{-1} B1 - C) x
+    // calculate B1 x
+    auto& B1x = m_vec_1_;
+    B1_.mv(x, B1x);
+    // calculate A^{-1} B1 x
+    auto& AinvB1x = m_vec_2_;
+    A_inv_.apply(B1x, AinvB1x);
+    // apply B2^T
+    B2_.mtv(AinvB1x, y);
+    // calculate Cx
+    auto& Cx = n_vec_1_;
+    C_.mv(x, Cx);
+    y -= Cx;
+  }
+
+
+  //! apply operator to x, scale and add:  \f$ y = y + \alpha S(x) \f$
+  virtual void applyscaleadd(FieldType alpha, const VectorBackend& x, VectorBackend& y) const override final
+  {
+    Vector x_la_vector(x);
+    Vector y_la_vector(y);
+    applyscaleadd(alpha, x_la_vector, y_la_vector);
+    y = y_la_vector.backend();
+  }
+
+  virtual void applyscaleadd(FieldType alpha, const Vector& x, Vector& y) const
+  {
+    auto Sx = n_vec_2_;
+    apply(x, Sx);
+    Sx *= alpha;
+    y += Sx;
+  }
+
+  //! Category of the linear operator (see SolverCategory::Category)
+  virtual SolverCategory::Category category() const override final
+  {
+    return SolverCategory::Category::sequential;
+  }
+
+  const Solver& A_inv() const
+  {
+    return A_inv_;
+  }
+
+  const Matrix& A() const
+  {
+    return A_;
+  }
+
+  const Matrix& B1() const
+  {
+    return B1_;
+  }
+
+  const Matrix& B2() const
+  {
+    return B2_;
+  }
+
+  const Matrix& C() const
+  {
+    return C_;
+  }
+
+private:
+  const Matrix& A_;
+  const Solver A_inv_;
+  const Matrix& B1_;
+  const Matrix& B2_;
+  const Matrix& C_;
+  const Common::Configuration solver_opts_;
+  // vectors to store intermediate results
+  mutable Vector m_vec_1_;
+  mutable Vector m_vec_2_;
+  mutable Vector n_vec_1_;
+  mutable Vector n_vec_2_;
+};
+
+#else // HAVE_DUNE_ISTL
+
+template <class FieldType = double, class CommunicatorType = SequentialCommunication>
+class SchurComplementOperator
+{
+  static_assert(Dune::AlwaysFalse<FieldType>::value, "You are missing dune-istl!");
+};
+
+#endif // HAVE_DUNE_ISTL
+
+} // namespace LA
+} // namespace XT
+} // namespace Dune
+
+#endif // DUNE_XT_LA_SOLVER_ISTL_SCHURCOMPLEMENT_HH
-- 
GitLab