// SPDX-FileCopyrightText: Copyright © DUNE Project contributors, see file LICENSE.md in module root
// SPDX-License-Identifier: LicenseRef-GPL-2.0-only-with-DUNE-exception
// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=4 sw=2 sts=2:
#ifndef DUNE_AMG_AMG_HH
#define DUNE_AMG_AMG_HH

#include <memory>
#include <sstream>
#include <dune/common/exceptions.hh>
#include <dune/istl/paamg/smoother.hh>
#include <dune/istl/paamg/transfer.hh>
#include <dune/istl/paamg/matrixhierarchy.hh>
#include <dune/istl/solvers.hh>
#include <dune/istl/scalarproducts.hh>
#include <dune/istl/superlu.hh>
#include <dune/istl/umfpack.hh>
#include <dune/istl/solvertype.hh>
#include <dune/istl/solverregistry.hh>
#include <dune/common/typetraits.hh>
#include <dune/common/exceptions.hh>
#include <dune/common/scalarvectorview.hh>
#include <dune/common/scalarmatrixview.hh>
#include <dune/common/parametertree.hh>

namespace Dune
{
  namespace Amg
  {
    /**
     * @defgroup ISTL_PAAMG Parallel Algebraic Multigrid
     * @ingroup ISTL_Prec
     * @brief A Parallel Algebraic Multigrid based on Agglomeration.
     */

    /**
     * @addtogroup ISTL_PAAMG
     *
     * @{
     */

    /** @file
     * @author Markus Blatt
     * @brief The AMG preconditioner.
     */

    template<class M, class X, class S, class P, class K, class A>
    class KAMG;

    template<class T>
    class KAmgTwoGrid;

    /**
     * @brief Parallel algebraic multigrid based on agglomeration.
     *
     * \tparam M The LinearOperator type which represents the matrix
     * \tparam X The vector type
     * \tparam S The smoother type
     * \tparam A An allocator for X
     *
     * \todo drop the smoother template parameter and replace with dynamic construction
     */
    template<class M, class X, class S, class PI=SequentialInformation,
        class A=std::allocator<X> >
    class AMG : public Preconditioner<X,X>
    {
      template<class M1, class X1, class S1, class P1, class K1, class A1>
      friend class KAMG;

      friend class KAmgTwoGrid<AMG>;

    public:
      /** @brief The matrix operator type. */
      typedef M Operator;
      /**
       * @brief The type of the parallel information.
       * Either OwnerOverlapCommunication or another type
       * describing the parallel data distribution and
       * providing communication methods.
       */
      typedef PI ParallelInformation;
      /** @brief The operator hierarchy type. */
      typedef MatrixHierarchy<M, ParallelInformation, A> OperatorHierarchy;
      /** @brief The parallal data distribution hierarchy type. */
      typedef typename OperatorHierarchy::ParallelInformationHierarchy ParallelInformationHierarchy;

      /** @brief The domain type. */
      typedef X Domain;
      /** @brief The range type. */
      typedef X Range;
      /** @brief the type of the coarse solver. */
      typedef InverseOperator<X,X> CoarseSolver;
      /**
       * @brief The type of the smoother.
       *
       * One of the preconditioners implementing the Preconditioner interface.
       * Note that the smoother has to fit the ParallelInformation.*/
      typedef S Smoother;

      /** @brief The argument type for the construction of the smoother. */
      typedef typename SmootherTraits<Smoother>::Arguments SmootherArgs;

      /**
       * @brief Construct a new amg with a specific coarse solver.
       * @param matrices The already set-up matrix hierarchy.
       * @param coarseSolver The set up solver to use on the coarse
       * grid, must match the coarse matrix in the matrix hierarchy.
       * @param smootherArgs The  arguments needed for thesmoother to use
       * for pre and post smoothing.
       * @param parms The parameters for the AMG.
       */
      AMG(OperatorHierarchy& matrices, CoarseSolver& coarseSolver,
          const SmootherArgs& smootherArgs, const Parameters& parms);

      /**
       * @brief Construct an AMG with an inexact coarse solver based on the smoother.
       *
       * As coarse solver a preconditioned CG method with the smoother as preconditioner
       * will be used. The matrix hierarchy is built automatically.
       * @param fineOperator The operator on the fine level.
       * @param criterion The criterion describing the coarsening strategy. E. g. SymmetricCriterion
       * or UnsymmetricCriterion, and providing the parameters.
       * @param smootherArgs The arguments for constructing the smoothers.
       * @param pinfo The information about the parallel distribution of the data.
       */
      template<class C>
      AMG(const Operator& fineOperator, const C& criterion,
          const SmootherArgs& smootherArgs=SmootherArgs(),
          const ParallelInformation& pinfo=ParallelInformation());

      /*!
         \brief Constructor an AMG via ParameterTree.

         \param fineOperator The operator on the fine level.
         \param configuration ParameterTree containing AMG parameters.
         \param pinfo Optionally, specify ParallelInformation

         ParameterTree Key         | Meaning
         --------------------------|------------
         smootherIterations        | The number of iterations to perform.
         smootherRelaxation        | The relaxation factor
         maxLevel                  | Maximum number of levels allowed in the hierarchy.
         coarsenTarget             | Maximum number of unknowns on the coarsest level.
         minCoarseningRate         | Coarsening will stop if the rate is below this threshold.
         accumulationMode          | If and how data is agglomerated on coarser level to
                                   | fewer processors. ("atOnce": do agglomeration once and
                                   | to one process; "successive": Multiple agglomerations to
                                   | fewer processes until all data is on one process;
                                   | "none": Do no agglomeration at all and solve coarse level
                                   | iteratively).
         prolongationDampingFactor | Damping factor for the prolongation.
         alpha                     | Scaling value for marking connections as strong.
         beta                      | Threshold for marking nodes as isolated.
         additive                  | Whether to use additive multigrid.
         gamma                     | 1 for V-cycle, 2 for W-cycle.
         preSteps                  | Number of presmoothing steps.
         postSteps                 | Number of postsmoothing steps.
         verbosity                 | Output verbosity. default=2.
         criterionSymmetric        | If true use SymmetricCriterion (default), else UnSymmetricCriterion
         strengthMeasure           | What conversion to use to convert a matrix block
                                   | to a scalar when determining strength of connection:
                                   | diagonal (use a diagonal of row diagonalRowIndex, class Diagonal, default);
                                   | rowSum (rowSum norm), frobenius (Frobenius norm);
                                   | one (use always one and neglect the actual entries).
          diagonalRowIndex         | The index to use for the diagonal strength (default 0)
                                   | if this is i and strengthMeasure is "diagonal", then
                                   | block[i][i] will be used when determining strength of
                                   | connection.
          defaultAggregationSizeMode | Whether to set default values depending on isotropy of
                                   | problem uses parameters "defaultAggregationDimension" and
                                   | "maxAggregateDistance" (isotropic: For and isotropic problem;
                                   |  anisotropic: for an anisotropic problem).
          defaultAggregationDimension | Dimension of the problem (used for setting default aggregate size).
          maxAggregateDistance     | Maximum distance in an aggregte (in term of minimum edges needed to travel.
                                   | one vertex to another within the aggregate).
          minAggregateSize         | Minimum number of vertices an aggregate should consist of.
          maxAggregateSize         | Maximum number of vertices an aggregate should consist of.

         See \ref ISTL_Factory for the ParameterTree layout and examples.
       */
      AMG(std::shared_ptr<const Operator> fineOperator, const ParameterTree& configuration, const ParallelInformation& pinfo=ParallelInformation());

      /**
       * @brief Copy constructor.
       */
      AMG(const AMG& amg);

      /** \copydoc Preconditioner::pre */
      void pre(Domain& x, Range& b);

      /** \copydoc Preconditioner::apply */
      void apply(Domain& v, const Range& d);

      //! Category of the preconditioner (see SolverCategory::Category)
      virtual SolverCategory::Category category() const
      {
        return category_;
      }

      /** \copydoc Preconditioner::post */
      void post(Domain& x);

      /**
       * @brief Get the aggregate number of each unknown on the coarsest level.
       * @param cont The random access container to store the numbers in.
       */
      template<class A1>
      void getCoarsestAggregateNumbers(std::vector<std::size_t,A1>& cont);

      std::size_t levels();

      std::size_t maxlevels();

      /**
       * @brief Recalculate the matrix hierarchy.
       *
       * It is assumed that the coarsening for the changed fine level
       * matrix would yield the same aggregates. In this case it suffices
       * to recalculate all the Galerkin products for the matrices of the
       * coarser levels.
       */
      void recalculateHierarchy()
      {
        matrices_->recalculateGalerkin(NegateSet<typename PI::OwnerSet>());
      }

      /**
       * @brief Check whether the coarse solver used is a direct solver.
       * @return True if the coarse level solver is a direct solver.
       */
      bool usesDirectCoarseLevelSolver() const;

    private:
      /*
       * @brief Helper function to create hierarchies with parameter tree.
       *
       * Will create the coarsen criterion with the norm and  create the
       * Hierarchies
       * \tparam Norm Type of the norm to use.
       */
      template<class Norm>
      void createCriterionAndHierarchies(std::shared_ptr<const Operator> matrixptr,
                                         const PI& pinfo, const Norm&,
                                         const ParameterTree& configuration,
                                         std::true_type compiles = std::true_type());
      template<class Norm>
      void createCriterionAndHierarchies(std::shared_ptr<const Operator> matrixptr,
                                         const PI& pinfo, const Norm&,
                                         const ParameterTree& configuration,
                                         std::false_type);
      /**
       * @brief Helper function to create hierarchies with settings from parameter tree.
       * @param criterion Coarsen criterion to configure and use
       */
      template<class C>
      void createHierarchies(C& criterion, std::shared_ptr<const Operator> matrixptr,
                             const PI& pinfo, const ParameterTree& configuration);
      /**
       * @brief Create matrix and smoother hierarchies.
       * @param criterion The coarsening criterion.
       * @param matrix The fine level matrix operator.
       * @param pinfo The fine level parallel information.
       */
      template<class C>
      void createHierarchies(C& criterion,
                             const std::shared_ptr<const Operator>& matrixptr,
                             const PI& pinfo);
      /**
       * @brief A struct that holds the context of the current level.
       *
       * These are the iterators to the smoother, matrix, parallel information,
       * and so on needed for the computations on the current level.
       */
      struct LevelContext
      {
        typedef Smoother SmootherType;
        /**
         * @brief The iterator over the smoothers.
         */
        typename Hierarchy<Smoother,A>::Iterator smoother;
        /**
         * @brief The iterator over the matrices.
         */
        typename OperatorHierarchy::ParallelMatrixHierarchy::ConstIterator matrix;
        /**
         * @brief The iterator over the parallel information.
         */
        typename ParallelInformationHierarchy::Iterator pinfo;
        /**
         * @brief The iterator over the redistribution information.
         */
        typename OperatorHierarchy::RedistributeInfoList::const_iterator redist;
        /**
         * @brief The iterator over the aggregates maps.
         */
        typename OperatorHierarchy::AggregatesMapList::const_iterator aggregates;
        /**
         * @brief The iterator over the left hand side.
         */
        typename Hierarchy<Domain,A>::Iterator lhs;
        /**
         * @brief The iterator over the updates.
         */
        typename Hierarchy<Domain,A>::Iterator update;
        /**
         * @brief The iterator over the right hand sided.
         */
        typename Hierarchy<Range,A>::Iterator rhs;
        /**
         * @brief The level index.
         */
        std::size_t level;
      };


      /**
       * @brief Multigrid cycle on a level.
       * @param levelContext the iterators of the current level.
       */
      void mgc(LevelContext& levelContext);

      void additiveMgc();

      /**
       * @brief Move the iterators to the finer level
       * @param levelContext the iterators of the current level.
       * @param processedFineLevel Whether the process computed on
       *         fine level or not.
       */
      void moveToFineLevel(LevelContext& levelContext,bool processedFineLevel);

      /**
       * @brief Move the iterators to the coarser level.
       * @param levelContext the iterators of the current level
       */
      bool moveToCoarseLevel(LevelContext& levelContext);

      /**
       * @brief Initialize iterators over levels with fine level.
       * @param levelContext the iterators of the current level
       */
      void initIteratorsWithFineLevel(LevelContext& levelContext);

      /**  @brief The matrix we solve. */
      std::shared_ptr<OperatorHierarchy> matrices_;
      /** @brief The arguments to construct the smoother */
      SmootherArgs smootherArgs_;
      /** @brief The hierarchy of the smoothers. */
      std::shared_ptr<Hierarchy<Smoother,A> > smoothers_;
      /** @brief The solver of the coarsest level. */
      std::shared_ptr<CoarseSolver> solver_;
      /** @brief The right hand side of our problem. */
      std::shared_ptr<Hierarchy<Range,A>> rhs_;
      /** @brief The left approximate solution of our problem. */
      std::shared_ptr<Hierarchy<Domain,A>> lhs_;
      /** @brief The total update for the outer solver. */
      std::shared_ptr<Hierarchy<Domain,A>> update_;
      /** @brief The type of the scalar product for the coarse solver. */
      using ScalarProduct = Dune::ScalarProduct<X>;
      /** @brief Scalar product on the coarse level. */
      std::shared_ptr<ScalarProduct> scalarProduct_;
      /** @brief Gamma, 1 for V-cycle and 2 for W-cycle. */
      std::size_t gamma_;
      /** @brief The number of pre and postsmoothing steps. */
      std::size_t preSteps_;
      /** @brief The number of postsmoothing steps. */
      std::size_t postSteps_;
      bool buildHierarchy_;
      bool additive;
      bool coarsesolverconverged;
      std::shared_ptr<Smoother> coarseSmoother_;
      /** @brief The solver category. */
      SolverCategory::Category category_;
      /** @brief The verbosity level. */
      std::size_t verbosity_;

      struct ToLower
      {
        std::string operator()(const std::string& str)
        {
          std::stringstream retval;
          std::ostream_iterator<char> out(retval);
          std::transform(str.begin(), str.end(), out,
                         [](char c){
                           return std::tolower(c, std::locale::classic());
                         });
          return retval.str();
        }
      };
    };

    template<class M, class X, class S, class PI, class A>
    inline AMG<M,X,S,PI,A>::AMG(const AMG& amg)
    : matrices_(amg.matrices_), smootherArgs_(amg.smootherArgs_),
      smoothers_(amg.smoothers_), solver_(amg.solver_),
      rhs_(), lhs_(), update_(),
      scalarProduct_(amg.scalarProduct_), gamma_(amg.gamma_),
      preSteps_(amg.preSteps_), postSteps_(amg.postSteps_),
      buildHierarchy_(amg.buildHierarchy_),
      additive(amg.additive), coarsesolverconverged(amg.coarsesolverconverged),
      coarseSmoother_(amg.coarseSmoother_),
      category_(amg.category_),
      verbosity_(amg.verbosity_)
    {}

    template<class M, class X, class S, class PI, class A>
    AMG<M,X,S,PI,A>::AMG(OperatorHierarchy& matrices, CoarseSolver& coarseSolver,
                         const SmootherArgs& smootherArgs,
                         const Parameters& parms)
      : matrices_(stackobject_to_shared_ptr(matrices)), smootherArgs_(smootherArgs),
        smoothers_(new Hierarchy<Smoother,A>), solver_(&coarseSolver),
        rhs_(), lhs_(), update_(), scalarProduct_(0),
        gamma_(parms.getGamma()), preSteps_(parms.getNoPreSmoothSteps()),
        postSteps_(parms.getNoPostSmoothSteps()), buildHierarchy_(false),
        additive(parms.getAdditive()), coarsesolverconverged(true),
        coarseSmoother_(),
// #warning should category be retrieved from matrices?
        category_(SolverCategory::category(*smoothers_->coarsest())),
        verbosity_(parms.debugLevel())
    {
      assert(matrices_->isBuilt());

      // build the necessary smoother hierarchies
      matrices_->coarsenSmoother(*smoothers_, smootherArgs_);
    }

    template<class M, class X, class S, class PI, class A>
    template<class C>
    AMG<M,X,S,PI,A>::AMG(const Operator& matrix,
                         const C& criterion,
                         const SmootherArgs& smootherArgs,
                         const PI& pinfo)
      : smootherArgs_(smootherArgs),
        smoothers_(new Hierarchy<Smoother,A>), solver_(),
        rhs_(), lhs_(), update_(), scalarProduct_(),
        gamma_(criterion.getGamma()), preSteps_(criterion.getNoPreSmoothSteps()),
        postSteps_(criterion.getNoPostSmoothSteps()), buildHierarchy_(true),
        additive(criterion.getAdditive()), coarsesolverconverged(true),
        coarseSmoother_(),
        category_(SolverCategory::category(pinfo)),
        verbosity_(criterion.debugLevel())
    {
      if(SolverCategory::category(matrix) != SolverCategory::category(pinfo))
        DUNE_THROW(InvalidSolverCategory, "Matrix and Communication must have the same SolverCategory!");
      // TODO: reestablish compile time checks.
      //static_assert(static_cast<int>(PI::category)==static_cast<int>(S::category),
      //             "Matrix and Solver must match in terms of category!");
      auto matrixptr = stackobject_to_shared_ptr(matrix);
      createHierarchies(criterion, matrixptr, pinfo);
    }

    template<class M, class X, class S, class PI, class A>
    AMG<M,X,S,PI,A>::AMG(std::shared_ptr<const Operator> matrixptr,
                         const ParameterTree& configuration,
                         const ParallelInformation& pinfo) :
      smoothers_(new Hierarchy<Smoother,A>),
      solver_(), rhs_(), lhs_(), update_(), scalarProduct_(), buildHierarchy_(true),
      coarsesolverconverged(true), coarseSmoother_(),
      category_(SolverCategory::category(pinfo))
    {

      if (configuration.hasKey ("smootherIterations"))
        smootherArgs_.iterations = configuration.get<int>("smootherIterations");

      if (configuration.hasKey ("smootherRelaxation"))
        smootherArgs_.relaxationFactor = configuration.get<typename SmootherArgs::RelaxationFactor>("smootherRelaxation");

      auto normName = ToLower()(configuration.get("strengthMeasure", "diagonal"));
      auto index =  configuration.get<int>("diagonalRowIndex", 0);

      if ( normName == "diagonal")
      {
        using field_type = typename M::field_type;
        using real_type = typename FieldTraits<field_type>::real_type;
        std::is_convertible<field_type, real_type> compiles;

        switch (index)
        {
        case 0:
          createCriterionAndHierarchies(matrixptr, pinfo, Diagonal<0>(), configuration, compiles);
          break;
        case 1:
          createCriterionAndHierarchies(matrixptr, pinfo, Diagonal<1>(), configuration, compiles);
          break;
        case 2:
          createCriterionAndHierarchies(matrixptr, pinfo, Diagonal<2>(), configuration, compiles);
          break;
        case 3:
          createCriterionAndHierarchies(matrixptr, pinfo, Diagonal<3>(), configuration, compiles);
          break;
        case 4:
          createCriterionAndHierarchies(matrixptr, pinfo, Diagonal<4>(), configuration, compiles);
          break;
        default:
          DUNE_THROW(InvalidStateException, "Currently strengthIndex>4 is not supported.");
        }
      }
      else if (normName == "rowsum")
        createCriterionAndHierarchies(matrixptr, pinfo, RowSum(), configuration);
      else if (normName == "frobenius")
        createCriterionAndHierarchies(matrixptr, pinfo, FrobeniusNorm(), configuration);
      else if (normName == "one")
        createCriterionAndHierarchies(matrixptr, pinfo, AlwaysOneNorm(), configuration);
      else
        DUNE_THROW(Dune::NotImplemented, "Wrong config file: strengthMeasure "<<normName<<" is not supported by AMG");
    }

  template<class M, class X, class S, class PI, class A>
  template<class Norm>
  void AMG<M,X,S,PI,A>::createCriterionAndHierarchies(std::shared_ptr<const Operator> matrixptr, const PI& pinfo, const Norm&, const ParameterTree& configuration, std::false_type)
  {
    DUNE_THROW(InvalidStateException, "Strength of connection measure does not support this type ("
               << className<typename M::field_type>() << ") as it is lacking a conversion to"
               << className<typename FieldTraits<typename M::field_type>::real_type>() << ".");
  }

  template<class M, class X, class S, class PI, class A>
  template<class Norm>
  void AMG<M,X,S,PI,A>::createCriterionAndHierarchies(std::shared_ptr<const Operator> matrixptr, const PI& pinfo, const Norm&, const ParameterTree& configuration, std::true_type)
  {
    if (configuration.get<bool>("criterionSymmetric", true))
      {
        using Criterion = Dune::Amg::CoarsenCriterion<
          Dune::Amg::SymmetricCriterion<typename M::matrix_type,Norm> >;
        Criterion criterion;
        createHierarchies(criterion, matrixptr, pinfo, configuration);
      }
      else
      {
        using Criterion = Dune::Amg::CoarsenCriterion<
          Dune::Amg::UnSymmetricCriterion<typename M::matrix_type,Norm> >;
        Criterion criterion;
        createHierarchies(criterion, matrixptr, pinfo, configuration);
      }
    }

  template<class M, class X, class S, class PI, class A>
  template<class C>
  void AMG<M,X,S,PI,A>::createHierarchies(C& criterion, std::shared_ptr<const Operator> matrixptr, const PI& pinfo, const ParameterTree& configuration)
  {
      if (configuration.hasKey ("maxLevel"))
        criterion.setMaxLevel(configuration.get<int>("maxLevel"));

      if (configuration.hasKey ("minCoarseningRate"))
        criterion.setMinCoarsenRate(configuration.get<int>("minCoarseningRate"));

      if (configuration.hasKey ("coarsenTarget"))
        criterion.setCoarsenTarget (configuration.get<int>("coarsenTarget"));

      if (configuration.hasKey ("accumulationMode"))
      {
        std::string mode = ToLower()(configuration.get<std::string>("accumulationMode"));
        if ( mode == "none")
          criterion.setAccumulate(AccumulationMode::noAccu);
        else if ( mode == "atonce" )
          criterion.setAccumulate(AccumulationMode::atOnceAccu);
        else if ( mode == "successive")
          criterion.setCoarsenTarget (AccumulationMode::successiveAccu);
        else
          DUNE_THROW(InvalidSolverFactoryConfiguration, "Parameter accumulationMode does not allow value "
                     << mode <<".");
      }

      if (configuration.hasKey ("prolongationDampingFactor"))
        criterion.setProlongationDampingFactor (configuration.get<double>("prolongationDampingFactor"));

      if (configuration.hasKey("defaultAggregationSizeMode"))
      {
        auto mode = ToLower()(configuration.get<std::string>("defaultAggregationSizeMode"));
        auto dim = configuration.get<std::size_t>("defaultAggregationDimension");
        std::size_t maxDistance = 2;
        if (configuration.hasKey("MaxAggregateDistance"))
          maxDistance = configuration.get<std::size_t>("maxAggregateDistance");
        if (mode == "isotropic")
          criterion.setDefaultValuesIsotropic(dim, maxDistance);
        else if(mode == "anisotropic")
          criterion.setDefaultValuesAnisotropic(dim, maxDistance);
        else
          DUNE_THROW(InvalidSolverFactoryConfiguration, "Parameter accumulationMode does not allow value "
                   << mode <<".");
      }

      if (configuration.hasKey("maxAggregateDistance"))
        criterion.setMaxDistance(configuration.get<std::size_t>("maxAggregateDistance"));

      if (configuration.hasKey("minAggregateSize"))
        criterion.setMinAggregateSize(configuration.get<std::size_t>("minAggregateSize"));

      if (configuration.hasKey("maxAggregateSize"))
        criterion.setMaxAggregateSize(configuration.get<std::size_t>("maxAggregateSize"));

      if (configuration.hasKey("maxAggregateConnectivity"))
        criterion.setMaxConnectivity(configuration.get<std::size_t>("maxAggregateConnectivity"));

      if (configuration.hasKey ("alpha"))
        criterion.setAlpha (configuration.get<double> ("alpha"));

      if (configuration.hasKey ("beta"))
        criterion.setBeta (configuration.get<double> ("beta"));

      if (configuration.hasKey ("gamma"))
        criterion.setGamma (configuration.get<std::size_t> ("gamma"));
      gamma_ = criterion.getGamma();

      if (configuration.hasKey ("additive"))
        criterion.setAdditive (configuration.get<bool>("additive"));
      additive = criterion.getAdditive();

      if (configuration.hasKey ("preSteps"))
        criterion.setNoPreSmoothSteps (configuration.get<std::size_t> ("preSteps"));
      preSteps_ = criterion.getNoPreSmoothSteps ();

      if (configuration.hasKey ("postSteps"))
        criterion.setNoPostSmoothSteps (configuration.get<std::size_t> ("postSteps"));
      postSteps_ = criterion.getNoPostSmoothSteps ();

      verbosity_ = configuration.get("verbosity", 0);
      criterion.setDebugLevel (verbosity_);

      createHierarchies(criterion, matrixptr, pinfo);
    }

    template <class Matrix,
              class Vector>
    struct DirectSolverSelector
    {
      typedef typename Matrix :: field_type field_type;
      enum SolverType { umfpack, superlu, none };

      static constexpr SolverType solver =
#if DISABLE_AMG_DIRECTSOLVER
        none;
#elif HAVE_SUITESPARSE_UMFPACK
        UMFPackMethodChooser< field_type > :: valid ? umfpack : none ;
#elif HAVE_SUPERLU
        superlu ;
#else
        none;
#endif

      template <class M, SolverType>
      struct Solver
      {
        typedef InverseOperator<Vector,Vector> type;
        static type* create(const M& mat, bool verbose, bool reusevector )
        {
          DUNE_THROW(NotImplemented,"DirectSolver not selected");
          return nullptr;
        }
        static std::string name () { return "None"; }
      };
#if HAVE_SUITESPARSE_UMFPACK
      template <class M>
      struct Solver< M, umfpack >
      {
        typedef UMFPack< M > type;
        static type* create(const M& mat, bool verbose, bool reusevector )
        {
          return new type(mat, verbose, reusevector );
        }
        static std::string name () { return "UMFPack"; }
      };
#endif
#if HAVE_SUPERLU
      template <class M>
      struct Solver< M, superlu >
      {
        typedef SuperLU< M > type;
        static type* create(const M& mat, bool verbose, bool reusevector )
        {
          return new type(mat, verbose, reusevector );
        }
        static std::string name () { return "SuperLU"; }
      };
#endif

      // define direct solver type to be used
      typedef Solver< Matrix, solver > SelectedSolver ;
      typedef typename SelectedSolver :: type   DirectSolver;
      static constexpr bool isDirectSolver = solver != none;
      static std::string name() { return SelectedSolver :: name (); }
      static DirectSolver* create(const Matrix& mat, bool verbose, bool reusevector )
      {
        return SelectedSolver :: create( mat, verbose, reusevector );
      }
    };

    template<class M, class X, class S, class PI, class A>
    template<class C>
    void AMG<M,X,S,PI,A>::createHierarchies(C& criterion,
      const std::shared_ptr<const Operator>& matrixptr,
                                            const PI& pinfo)
    {
      Timer watch;
      matrices_ = std::make_shared<OperatorHierarchy>(
        std::const_pointer_cast<Operator>(matrixptr),
        stackobject_to_shared_ptr(const_cast<PI&>(pinfo)));

      matrices_->template build<NegateSet<typename PI::OwnerSet> >(criterion);

      // build the necessary smoother hierarchies
      matrices_->coarsenSmoother(*smoothers_, smootherArgs_);

      // test whether we should solve on the coarse level. That is the case if we
      // have that level and if there was a redistribution on this level then our
      // communicator has to be valid (size()>0) as the smoother might try to communicate
      // in the constructor.
      if(buildHierarchy_ && matrices_->levels()==matrices_->maxlevels()
         && ( ! matrices_->redistributeInformation().back().isSetup() ||
              matrices_->parallelInformation().coarsest().getRedistributed().communicator().size() ) )
      {
        // We have the carsest level. Create the coarse Solver
        SmootherArgs sargs(smootherArgs_);
        sargs.iterations = 1;

        typename ConstructionTraits<Smoother>::Arguments cargs;
        cargs.setArgs(sargs);
        if(matrices_->redistributeInformation().back().isSetup()) {
          // Solve on the redistributed partitioning
          cargs.setMatrix(matrices_->matrices().coarsest().getRedistributed().getmat());
          cargs.setComm(matrices_->parallelInformation().coarsest().getRedistributed());
        }else{
          cargs.setMatrix(matrices_->matrices().coarsest()->getmat());
          cargs.setComm(*matrices_->parallelInformation().coarsest());
        }

        coarseSmoother_ = ConstructionTraits<Smoother>::construct(cargs);
        scalarProduct_ = createScalarProduct<X>(cargs.getComm(),category());

        typedef DirectSolverSelector< typename M::matrix_type, X > SolverSelector;

        // Use superlu if we are purely sequential or with only one processor on the coarsest level.
        if( SolverSelector::isDirectSolver &&
            (std::is_same<ParallelInformation,SequentialInformation>::value // sequential mode
           || matrices_->parallelInformation().coarsest()->communicator().size()==1 //parallel mode and only one processor
           || (matrices_->parallelInformation().coarsest().isRedistributed()
               && matrices_->parallelInformation().coarsest().getRedistributed().communicator().size()==1
               && matrices_->parallelInformation().coarsest().getRedistributed().communicator().size()>0) )
          )
        { // redistribute and 1 proc
          if(matrices_->parallelInformation().coarsest().isRedistributed())
          {
            if(matrices_->matrices().coarsest().getRedistributed().getmat().N()>0)
            {
              // We are still participating on this level
              solver_.reset(SolverSelector::create(matrices_->matrices().coarsest().getRedistributed().getmat(), false, false));
            }
            else
              solver_.reset();
          }
          else
          {
            solver_.reset(SolverSelector::create(matrices_->matrices().coarsest()->getmat(), false, false));
          }
          if(verbosity_>0 && matrices_->parallelInformation().coarsest()->communicator().rank()==0)
            std::cout<< "Using a direct coarse solver (" << SolverSelector::name() << ")" << std::endl;
        }
        else
        {
          if(matrices_->parallelInformation().coarsest().isRedistributed())
          {
            if(matrices_->matrices().coarsest().getRedistributed().getmat().N()>0)
              // We are still participating on this level

              // we have to allocate these types using the rebound allocator
              // in order to ensure that we fulfill the alignment requirements
              solver_.reset(new BiCGSTABSolver<X>(const_cast<M&>(matrices_->matrices().coarsest().getRedistributed()),
                                                  *scalarProduct_,
                                                  *coarseSmoother_, 1E-2, 1000, 0));
            else
              solver_.reset();
          }else
          {
              solver_.reset(new BiCGSTABSolver<X>(const_cast<M&>(*matrices_->matrices().coarsest()),
                  *scalarProduct_,
                  *coarseSmoother_, 1E-2, 1000, 0));
            // // we have to allocate these types using the rebound allocator
            // // in order to ensure that we fulfill the alignment requirements
            // using Alloc = typename std::allocator_traits<A>::template rebind_alloc<BiCGSTABSolver<X>>;
            // Alloc alloc;
            // auto p = alloc.allocate(1);
            // std::allocator_traits<Alloc>::construct(alloc, p,
            //   const_cast<M&>(*matrices_->matrices().coarsest()),
            //   *scalarProduct_,
            //   *coarseSmoother_, 1E-2, 1000, 0);
            // solver_.reset(p,[](BiCGSTABSolver<X>* p){
            //     Alloc alloc;
            //     std::allocator_traits<Alloc>::destroy(alloc, p);
            //     alloc.deallocate(p,1);
            //   });
          }
        }
      }

      if(verbosity_>0 && matrices_->parallelInformation().finest()->communicator().rank()==0)
        std::cout<<"Building hierarchy of "<<matrices_->maxlevels()<<" levels "
                 <<"(including coarse solver) took "<<watch.elapsed()<<" seconds."<<std::endl;
    }


    template<class M, class X, class S, class PI, class A>
    void AMG<M,X,S,PI,A>::pre(Domain& x, Range& b)
    {
      // Detect Matrix rows where all offdiagonal entries are
      // zero and set x such that  A_dd*x_d=b_d
      // Thus users can be more careless when setting up their linear
      // systems.
      typedef typename M::matrix_type Matrix;
      typedef typename Matrix::ConstRowIterator RowIter;
      typedef typename Matrix::ConstColIterator ColIter;
      typedef typename Matrix::block_type Block;
      Block zero;
      zero=typename Matrix::field_type();

      const Matrix& mat=matrices_->matrices().finest()->getmat();
      for(RowIter row=mat.begin(); row!=mat.end(); ++row) {
        bool isDirichlet = true;
        bool hasDiagonal = false;
        Block diagonal{};
        for(ColIter col=row->begin(); col!=row->end(); ++col) {
          if(row.index()==col.index()) {
            diagonal = *col;
            hasDiagonal = true;
          }else{
            if(*col!=zero)
              isDirichlet = false;
          }
        }
        if(isDirichlet && hasDiagonal)
        {
          auto&& xEntry = Impl::asVector(x[row.index()]);
          auto&& bEntry = Impl::asVector(b[row.index()]);
          Impl::asMatrix(diagonal).solve(xEntry, bEntry);
        }
      }

      if(smoothers_->levels()>0)
        smoothers_->finest()->pre(x,b);
      else
        // No smoother to make x consistent! Do it by hand
        matrices_->parallelInformation().coarsest()->copyOwnerToAll(x,x);
      rhs_ = std::make_shared<Hierarchy<Range,A>>(std::make_shared<Range>(b));
      lhs_ = std::make_shared<Hierarchy<Domain,A>>(std::make_shared<Domain>(x));
      update_ = std::make_shared<Hierarchy<Domain,A>>(std::make_shared<Domain>(x));
      matrices_->coarsenVector(*rhs_);
      matrices_->coarsenVector(*lhs_);
      matrices_->coarsenVector(*update_);

      // Preprocess all smoothers
      typedef typename Hierarchy<Smoother,A>::Iterator Iterator;
      typedef typename Hierarchy<Range,A>::Iterator RIterator;
      typedef typename Hierarchy<Domain,A>::Iterator DIterator;
      Iterator coarsest = smoothers_->coarsest();
      Iterator smoother = smoothers_->finest();
      RIterator rhs = rhs_->finest();
      DIterator lhs = lhs_->finest();
      if(smoothers_->levels()>1) {

        assert(lhs_->levels()==rhs_->levels());
        assert(smoothers_->levels()==lhs_->levels() || matrices_->levels()==matrices_->maxlevels());
        assert(smoothers_->levels()+1==lhs_->levels() || matrices_->levels()<matrices_->maxlevels());

        if(smoother!=coarsest)
          for(++smoother, ++lhs, ++rhs; smoother != coarsest; ++smoother, ++lhs, ++rhs)
            smoother->pre(*lhs,*rhs);
        smoother->pre(*lhs,*rhs);
      }


      // The preconditioner might change x and b. So we have to
      // copy the changes to the original vectors.
      x = *lhs_->finest();
      b = *rhs_->finest();

    }
    template<class M, class X, class S, class PI, class A>
    std::size_t AMG<M,X,S,PI,A>::levels()
    {
      return matrices_->levels();
    }
    template<class M, class X, class S, class PI, class A>
    std::size_t AMG<M,X,S,PI,A>::maxlevels()
    {
      return matrices_->maxlevels();
    }

    /** \copydoc Preconditioner::apply */
    template<class M, class X, class S, class PI, class A>
    void AMG<M,X,S,PI,A>::apply(Domain& v, const Range& d)
    {
      LevelContext levelContext;

      if(additive) {
        *(rhs_->finest())=d;
        additiveMgc();
        v=*lhs_->finest();
      }else{
        // Init all iterators for the current level
        initIteratorsWithFineLevel(levelContext);


        *levelContext.lhs = v;
        *levelContext.rhs = d;
        *levelContext.update=0;
        levelContext.level=0;

        mgc(levelContext);

        if(postSteps_==0||matrices_->maxlevels()==1)
          levelContext.pinfo->copyOwnerToAll(*levelContext.update, *levelContext.update);

        v=*levelContext.update;
      }

    }

    template<class M, class X, class S, class PI, class A>
    void AMG<M,X,S,PI,A>::initIteratorsWithFineLevel(LevelContext& levelContext)
    {
      levelContext.smoother = smoothers_->finest();
      levelContext.matrix = matrices_->matrices().finest();
      levelContext.pinfo = matrices_->parallelInformation().finest();
      levelContext.redist =
        matrices_->redistributeInformation().begin();
      levelContext.aggregates = matrices_->aggregatesMaps().begin();
      levelContext.lhs = lhs_->finest();
      levelContext.update = update_->finest();
      levelContext.rhs = rhs_->finest();
    }

    template<class M, class X, class S, class PI, class A>
    bool AMG<M,X,S,PI,A>
    ::moveToCoarseLevel(LevelContext& levelContext)
    {

      bool processNextLevel=true;

      if(levelContext.redist->isSetup()) {
        levelContext.redist->redistribute(static_cast<const Range&>(*levelContext.rhs),
                             levelContext.rhs.getRedistributed());
        processNextLevel = levelContext.rhs.getRedistributed().size()>0;
        if(processNextLevel) {
          //restrict defect to coarse level right hand side.
          typename Hierarchy<Range,A>::Iterator fineRhs = levelContext.rhs++;
          ++levelContext.pinfo;
          Transfer<typename OperatorHierarchy::AggregatesMap::AggregateDescriptor,Range,ParallelInformation>
          ::restrictVector(*(*levelContext.aggregates), *levelContext.rhs,
                           static_cast<const Range&>(fineRhs.getRedistributed()),
                           *levelContext.pinfo);
        }
      }else{
        //restrict defect to coarse level right hand side.
        typename Hierarchy<Range,A>::Iterator fineRhs = levelContext.rhs++;
        ++levelContext.pinfo;
        Transfer<typename OperatorHierarchy::AggregatesMap::AggregateDescriptor,Range,ParallelInformation>
        ::restrictVector(*(*levelContext.aggregates),
                         *levelContext.rhs, static_cast<const Range&>(*fineRhs),
                         *levelContext.pinfo);
      }

      if(processNextLevel) {
        // prepare coarse system
        ++levelContext.lhs;
        ++levelContext.update;
        ++levelContext.matrix;
        ++levelContext.level;
        ++levelContext.redist;

        if(levelContext.matrix != matrices_->matrices().coarsest() || matrices_->levels()<matrices_->maxlevels()) {
          // next level is not the globally coarsest one
          ++levelContext.smoother;
          ++levelContext.aggregates;
        }
        // prepare the update on the next level
        *levelContext.update=0;
      }
      return processNextLevel;
    }

    template<class M, class X, class S, class PI, class A>
    void AMG<M,X,S,PI,A>
    ::moveToFineLevel(LevelContext& levelContext, bool processNextLevel)
    {
      if(processNextLevel) {
        if(levelContext.matrix != matrices_->matrices().coarsest() || matrices_->levels()<matrices_->maxlevels()) {
          // previous level is not the globally coarsest one
          --levelContext.smoother;
          --levelContext.aggregates;
        }
        --levelContext.redist;
        --levelContext.level;
        //prolongate and add the correction (update is in coarse left hand side)
        --levelContext.matrix;

        //typename Hierarchy<Domain,A>::Iterator coarseLhs = lhs--;
        --levelContext.lhs;
        --levelContext.pinfo;
      }
      if(levelContext.redist->isSetup()) {
        // Need to redistribute during prolongateVector
        levelContext.lhs.getRedistributed()=0;
        Transfer<typename OperatorHierarchy::AggregatesMap::AggregateDescriptor,Range,ParallelInformation>
        ::prolongateVector(*(*levelContext.aggregates), *levelContext.update, *levelContext.lhs,
                           levelContext.lhs.getRedistributed(),
                           matrices_->getProlongationDampingFactor(),
                           *levelContext.pinfo, *levelContext.redist);
      }else{
        *levelContext.lhs=0;
        Transfer<typename OperatorHierarchy::AggregatesMap::AggregateDescriptor,Range,ParallelInformation>
        ::prolongateVector(*(*levelContext.aggregates), *levelContext.update, *levelContext.lhs,
                           matrices_->getProlongationDampingFactor(),
                           *levelContext.pinfo);
      }


      if(processNextLevel) {
        --levelContext.update;
        --levelContext.rhs;
      }

      *levelContext.update += *levelContext.lhs;
    }

    template<class M, class X, class S, class PI, class A>
    bool AMG<M,X,S,PI,A>::usesDirectCoarseLevelSolver() const
    {
      return IsDirectSolver< CoarseSolver>::value;
    }

    template<class M, class X, class S, class PI, class A>
    void AMG<M,X,S,PI,A>::mgc(LevelContext& levelContext){
      if(levelContext.matrix == matrices_->matrices().coarsest() && levels()==maxlevels()) {
        // Solve directly
        InverseOperatorResult res;
        res.converged=true; // If we do not compute this flag will not get updated
        if(levelContext.redist->isSetup()) {
          levelContext.redist->redistribute(*levelContext.rhs, levelContext.rhs.getRedistributed());
          if(levelContext.rhs.getRedistributed().size()>0) {
            // We are still participating in the computation
            levelContext.pinfo.getRedistributed().copyOwnerToAll(levelContext.rhs.getRedistributed(),
                                                    levelContext.rhs.getRedistributed());
            solver_->apply(levelContext.update.getRedistributed(),
                           levelContext.rhs.getRedistributed(), res);
          }
          levelContext.redist->redistributeBackward(*levelContext.update, levelContext.update.getRedistributed());
          levelContext.pinfo->copyOwnerToAll(*levelContext.update, *levelContext.update);
        }else{
          levelContext.pinfo->copyOwnerToAll(*levelContext.rhs, *levelContext.rhs);
          solver_->apply(*levelContext.update, *levelContext.rhs, res);
        }

        if (!res.converged)
          coarsesolverconverged = false;
      }else{
        // presmoothing
        presmooth(levelContext, preSteps_);

#ifndef DUNE_AMG_NO_COARSEGRIDCORRECTION
        bool processNextLevel = moveToCoarseLevel(levelContext);

        if(processNextLevel) {
          // next level
          for(std::size_t i=0; i<gamma_; i++){
            mgc(levelContext);
            if (levelContext.matrix == matrices_->matrices().coarsest() && levels()==maxlevels())
              break;
            if(i+1 < gamma_){
              levelContext.matrix->applyscaleadd(-1., *levelContext.lhs, *levelContext.rhs);
            }
          }
        }

        moveToFineLevel(levelContext, processNextLevel);
#else
        *lhs=0;
#endif

        if(levelContext.matrix == matrices_->matrices().finest()) {
          coarsesolverconverged = matrices_->parallelInformation().finest()->communicator().prod(coarsesolverconverged);
          if(!coarsesolverconverged)
            DUNE_THROW(MathError, "Coarse solver did not converge");
        }
        // postsmoothing
        postsmooth(levelContext, postSteps_);

      }
    }

    template<class M, class X, class S, class PI, class A>
    void AMG<M,X,S,PI,A>::additiveMgc(){

      // restrict residual to all levels
      typename ParallelInformationHierarchy::Iterator pinfo=matrices_->parallelInformation().finest();
      typename Hierarchy<Range,A>::Iterator rhs=rhs_->finest();
      typename Hierarchy<Domain,A>::Iterator lhs = lhs_->finest();
      typename OperatorHierarchy::AggregatesMapList::const_iterator aggregates=matrices_->aggregatesMaps().begin();

      for(typename Hierarchy<Range,A>::Iterator fineRhs=rhs++; fineRhs != rhs_->coarsest(); fineRhs=rhs++, ++aggregates) {
        ++pinfo;
        Transfer<typename OperatorHierarchy::AggregatesMap::AggregateDescriptor,Range,ParallelInformation>
        ::restrictVector(*(*aggregates), *rhs, static_cast<const Range&>(*fineRhs), *pinfo);
      }

      // pinfo is invalid, set to coarsest level
      //pinfo = matrices_->parallelInformation().coarsest
      // calculate correction for all levels
      lhs = lhs_->finest();
      typename Hierarchy<Smoother,A>::Iterator smoother = smoothers_->finest();

      for(rhs=rhs_->finest(); rhs != rhs_->coarsest(); ++lhs, ++rhs, ++smoother) {
        // presmoothing
        *lhs=0;
        smoother->apply(*lhs, *rhs);
      }

      // Coarse level solve
#ifndef DUNE_AMG_NO_COARSEGRIDCORRECTION
      InverseOperatorResult res;
      pinfo->copyOwnerToAll(*rhs, *rhs);
      solver_->apply(*lhs, *rhs, res);

      if(!res.converged)
        DUNE_THROW(MathError, "Coarse solver did not converge");
#else
      *lhs=0;
#endif
      // Prologate and add up corrections from all levels
      --pinfo;
      --aggregates;

      for(typename Hierarchy<Domain,A>::Iterator coarseLhs = lhs--; coarseLhs != lhs_->finest(); coarseLhs = lhs--, --aggregates, --pinfo) {
        Transfer<typename OperatorHierarchy::AggregatesMap::AggregateDescriptor,Range,ParallelInformation>
        ::prolongateVector(*(*aggregates), *coarseLhs, *lhs, 1.0, *pinfo);
      }
    }


    /** \copydoc Preconditioner::post */
    template<class M, class X, class S, class PI, class A>
    void AMG<M,X,S,PI,A>::post([[maybe_unused]] Domain& x)
    {
      // Postprocess all smoothers
      typedef typename Hierarchy<Smoother,A>::Iterator Iterator;
      typedef typename Hierarchy<Domain,A>::Iterator DIterator;
      Iterator coarsest = smoothers_->coarsest();
      Iterator smoother = smoothers_->finest();
      DIterator lhs = lhs_->finest();
      if(smoothers_->levels()>0) {
        if(smoother != coarsest  || matrices_->levels()<matrices_->maxlevels())
          smoother->post(*lhs);
        if(smoother!=coarsest)
          for(++smoother, ++lhs; smoother != coarsest; ++smoother, ++lhs)
            smoother->post(*lhs);
        smoother->post(*lhs);
      }
      lhs_ = nullptr;
      update_ = nullptr;
      rhs_ = nullptr;
    }

    template<class M, class X, class S, class PI, class A>
    template<class A1>
    void AMG<M,X,S,PI,A>::getCoarsestAggregateNumbers(std::vector<std::size_t,A1>& cont)
    {
      matrices_->getCoarsestAggregatesOnFinest(cont);
    }

  } // end namespace Amg

  struct AMGCreator{
    template<class> struct isValidMatrix : std::false_type{};
    template<class T, int n, int m, class A> struct isValidMatrix<BCRSMatrix<FieldMatrix<T,n,m>, A>> : std::true_type{};

    template<class OP>
    std::shared_ptr<Dune::Preconditioner<typename OP::element_type::domain_type, typename OP::element_type::range_type> >
    makeAMG(const OP& op, const std::string& smoother, const Dune::ParameterTree& config) const
    {
      DUNE_THROW(Dune::Exception, "Operator type not supported by AMG");
    }

    template<class M, class X, class Y>
    std::shared_ptr<Dune::Preconditioner<X,Y> >
    makeAMG(const std::shared_ptr<MatrixAdapter<M,X,Y>>& op, const std::string& smoother,
            const Dune::ParameterTree& config) const
    {
      using OP = MatrixAdapter<M,X,Y>;

      if(smoother == "ssor")
        return std::make_shared<Amg::AMG<OP, X, SeqSSOR<M,X,Y>>>(op, config);
      if(smoother == "sor")
        return std::make_shared<Amg::AMG<OP, X, SeqSOR<M,X,Y>>>(op, config);
      if(smoother == "jac")
        return std::make_shared<Amg::AMG<OP, X, SeqJac<M,X,Y>>>(op, config);
      if(smoother == "gs")
        return std::make_shared<Amg::AMG<OP, X, SeqGS<M,X,Y>>>(op, config);
      if(smoother == "ilu")
        return std::make_shared<Amg::AMG<OP, X, SeqILU<M,X,Y>>>(op, config);
      else
        DUNE_THROW(Dune::Exception, "Unknown smoother for AMG");
    }

    template<class M, class X, class Y, class C>
    std::shared_ptr<Dune::Preconditioner<X,Y> >
    makeAMG(const std::shared_ptr<OverlappingSchwarzOperator<M,X,Y,C>>& op, const std::string& smoother,
            const Dune::ParameterTree& config) const
    {
      using OP = OverlappingSchwarzOperator<M,X,Y,C>;

      auto cop = std::static_pointer_cast<const OP>(op);

      if(smoother == "ssor")
        return std::make_shared<Amg::AMG<OP, X, BlockPreconditioner<X,Y,C,SeqSSOR<M,X,Y>>,C>>(cop, config, op->getCommunication());
      if(smoother == "sor")
        return std::make_shared<Amg::AMG<OP, X, BlockPreconditioner<X,Y,C,SeqSOR<M,X,Y>>,C>>(cop, config, op->getCommunication());
      if(smoother == "jac")
        return std::make_shared<Amg::AMG<OP, X, BlockPreconditioner<X,Y,C,SeqJac<M,X,Y>>,C>>(cop, config, op->getCommunication());
      if(smoother == "gs")
        return std::make_shared<Amg::AMG<OP, X, BlockPreconditioner<X,Y,C,SeqGS<M,X,Y>>,C>>(cop, config, op->getCommunication());
      if(smoother == "ilu")
        return std::make_shared<Amg::AMG<OP, X, BlockPreconditioner<X,Y,C,SeqILU<M,X,Y>>,C>>(cop, config, op->getCommunication());
      else
        DUNE_THROW(Dune::Exception, "Unknown smoother for AMG");
    }

    template<class M, class X, class Y, class C>
    std::shared_ptr<Dune::Preconditioner<X,Y> >
    makeAMG(const std::shared_ptr<NonoverlappingSchwarzOperator<M,X,Y,C>>& op, const std::string& smoother,
            const Dune::ParameterTree& config) const
    {
      using OP = NonoverlappingSchwarzOperator<M,X,Y,C>;

      if(smoother == "ssor")
        return std::make_shared<Amg::AMG<OP, X, NonoverlappingBlockPreconditioner<C,SeqSSOR<M,X,Y>>,C>>(op, config, op->getCommunication());
      if(smoother == "sor")
        return std::make_shared<Amg::AMG<OP, X, NonoverlappingBlockPreconditioner<C,SeqSOR<M,X,Y>>,C>>(op, config, op->getCommunication());
      if(smoother == "jac")
        return std::make_shared<Amg::AMG<OP, X, NonoverlappingBlockPreconditioner<C,SeqJac<M,X,Y>>,C>>(op, config, op->getCommunication());
      if(smoother == "gs")
        return std::make_shared<Amg::AMG<OP, X, NonoverlappingBlockPreconditioner<C,SeqGS<M,X,Y>>,C>>(op, config, op->getCommunication());
      if(smoother == "ilu")
        return std::make_shared<Amg::AMG<OP, X, NonoverlappingBlockPreconditioner<C,SeqILU<M,X,Y>>,C>>(op, config, op->getCommunication());
      else
        DUNE_THROW(Dune::Exception, "Unknown smoother for AMG");
    }

    template<typename OpTraits, typename OP>
    std::shared_ptr<Dune::Preconditioner<typename OpTraits::domain_type,
                                         typename OpTraits::range_type>>
    operator() (OpTraits opTraits, const std::shared_ptr<OP>& op, const Dune::ParameterTree& config,
                std::enable_if_t<isValidMatrix<typename OpTraits::matrix_type>::value,int> = 0) const
    {
      using field_type = typename OpTraits::matrix_type::field_type;
      using real_type = typename FieldTraits<field_type>::real_type;
      if (!std::is_convertible<field_type, real_type>())
        DUNE_THROW(UnsupportedType, "AMG needs field_type(" <<
                   className<field_type>() <<
                   ") to be convertible to its real_type (" <<
                   className<real_type>() <<
                   ").");
      std::string smoother = config.get("smoother", "ssor");
      // we can irgnore the OpTraits here. As the AMG can only work
      // with actual matrices, the operator op must be of type
      // MatrixAdapter or *SchwarzOperator. In any case these
      // operators provide all necessary information about matrix,
      // domain and range type
      return makeAMG(op, smoother, config);
    }

    template<typename OpTraits, typename OP>
    std::shared_ptr<Dune::Preconditioner<typename OpTraits::domain_type,
                                         typename OpTraits::range_type>>
    operator() (OpTraits opTraits, const std::shared_ptr<OP>& op, const Dune::ParameterTree& config,
                std::enable_if_t<!isValidMatrix<typename OpTraits::matrix_type>::value,int> = 0) const
    {
      DUNE_THROW(UnsupportedType, "AMG needs a FieldMatrix as Matrix block_type");
    }
  };

  DUNE_REGISTER_PRECONDITIONER("amg", AMGCreator());
} // end namespace Dune

#endif
