/* Copyright 2009, 2010 SINTEF ICT, Applied Mathematics. Copyright 2019 SINTEF Digital, Mathematics and Cybernetics. This file is part of the Open Porous Media project (OPM). OPM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OPM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OPM. If not, see . */ #ifndef OPM_PRECONDITIONERFACTORY_HEADER #define OPM_PRECONDITIONERFACTORY_HEADER #include #include #include #include #include #include #include #include #include #include #include #include namespace Opm { /// This is an object factory for creating preconditioners. The /// user need only interact with the factory through the static /// methods addStandardPreconditioners() and create(). In addition /// a user can call the addCreator() static method to add further /// preconditioners. template class PreconditionerFactory { public: /// Linear algebra types. using Matrix = typename Operator::matrix_type; using Vector = typename Operator::domain_type; // Assuming symmetry: that domain and range types are the same. /// The type of pointer returned by create(). using PrecPtr = std::shared_ptr>; /// The type of creator functions passed to addCreator(). using Creator = std::function&)>; using ParCreator = std::function&, const Comm&)>; /// Create a new serial preconditioner and return a pointer to it. /// \param op operator to be preconditioned. /// \param prm parameters for the preconditioner, in particular its type. /// \param weightsCalculator Calculator for weights used in CPR. /// \return (smart) pointer to the created preconditioner. static PrecPtr create(const Operator& op, const boost::property_tree::ptree& prm, const std::function& weightsCalculator = std::function()) { return instance().doCreate(op, prm, weightsCalculator); } /// Create a new parallel preconditioner and return a pointer to it. /// \param op operator to be preconditioned. /// \param prm parameters for the preconditioner, in particular its type. /// \param comm communication object (typically OwnerOverlapCopyCommunication). /// \param weightsCalculator Calculator for weights used in CPR. /// \return (smart) pointer to the created preconditioner. static PrecPtr create(const Operator& op, const boost::property_tree::ptree& prm, const std::function& weightsCalculator, const Comm& comm) { return instance().doCreate(op, prm, weightsCalculator, comm); } /// Create a new parallel preconditioner and return a pointer to it. /// \param op operator to be preconditioned. /// \param prm parameters for the preconditioner, in particular its type. /// \param comm communication object (typically OwnerOverlapCopyCommunication). /// \return (smart) pointer to the created preconditioner. static PrecPtr create(const Operator& op, const boost::property_tree::ptree& prm, const Comm& comm) { return instance().doCreate(op, prm, std::function(), comm); } /// Add a creator for a serial preconditioner to the PreconditionerFactory. /// After the call, the user may obtain a preconditioner by /// calling create() with the given type string as a parameter /// contained in the property_tree. /// \param type the type string we want the PreconditionerFactory to /// associate with the preconditioner. /// \param creator a function or lambda creating a preconditioner. static void addCreator(const std::string& type, Creator creator) { instance().doAddCreator(type, creator); } /// Add a creator for a parallel preconditioner to the PreconditionerFactory. /// After the call, the user may obtain a preconditioner by /// calling create() with the given type string as a parameter /// contained in the property_tree. /// \param type the type string we want the PreconditionerFactory to /// associate with the preconditioner. /// \param creator a function or lambda creating a preconditioner. static void addCreator(const std::string& type, ParCreator creator) { instance().doAddCreator(type, creator); } private: using CriterionBase = Dune::Amg::AggregationCriterion>; using Criterion = Dune::Amg::CoarsenCriterion; // Helpers for creation of AMG preconditioner. static Criterion amgCriterion(const boost::property_tree::ptree& prm) { Criterion criterion(15, prm.get("coarsenTarget", 1200)); criterion.setDefaultValuesIsotropic(2); criterion.setAlpha(prm.get("alpha", 0.33)); criterion.setBeta(prm.get("beta", 1e-5)); criterion.setMaxLevel(prm.get("maxlevel", 15)); criterion.setSkipIsolated(prm.get("skip_isolated", false)); criterion.setNoPreSmoothSteps(prm.get("pre_smooth", 1)); criterion.setNoPostSmoothSteps(prm.get("post_smooth", 1)); criterion.setDebugLevel(prm.get("verbosity", 0)); // As the default we request to accumulate data to 1 process always as our matrix // graph might be unsymmetric and hence not supported by the PTScotch/ParMetis // calls in DUNE. Accumulating to 1 skips PTScotch/ParMetis criterion.setAccumulate(static_cast(prm.get("accumulate", 1))); criterion.setProlongationDampingFactor(prm.get("prolongationdamping", 1.6)); criterion.setMaxDistance(prm.get("maxdistance", 2)); criterion.setMaxConnectivity(prm.get("maxconnectivity", 15)); criterion.setMaxAggregateSize(prm.get("maxaggsize", 6)); criterion.setMinAggregateSize(prm.get("minaggsize", 4)); return criterion; } /// Helper struct to explicitly overload amgSmootherArgs() version for /// ParallelOverlappingILU0, since in-class specialization is not allowed. template struct Id { using Type = X; }; template static auto amgSmootherArgs(const boost::property_tree::ptree& prm) { return amgSmootherArgs(prm, Id()); } template static auto amgSmootherArgs(const boost::property_tree::ptree& prm, Id) { using SmootherArgs = typename Dune::Amg::SmootherTraits::Arguments; SmootherArgs smootherArgs; smootherArgs.iterations = prm.get("iterations", 1); // smootherArgs.overlap=SmootherArgs::vertex; // smootherArgs.overlap=SmootherArgs::none; // smootherArgs.overlap=SmootherArgs::aggregate; smootherArgs.relaxationFactor = prm.get("relaxation", 1.0); return smootherArgs; } static auto amgSmootherArgs(const boost::property_tree::ptree& prm, Id>) { using Smoother = Opm::ParallelOverlappingILU0; using SmootherArgs = typename Dune::Amg::SmootherTraits::Arguments; SmootherArgs smootherArgs; smootherArgs.iterations = prm.get("iterations", 1); const int iluwitdh = prm.get("iluwidth", 0); smootherArgs.setN(iluwitdh); const MILU_VARIANT milu = convertString2Milu(prm.get("milutype", std::string("ilu"))); smootherArgs.setMilu(milu); // smootherArgs.overlap=SmootherArgs::vertex; // smootherArgs.overlap=SmootherArgs::none; // smootherArgs.overlap=SmootherArgs::aggregate; smootherArgs.relaxationFactor = prm.get("relaxation", 1.0); return smootherArgs; } template static PrecPtr makeAmgPreconditioner(const Operator& op, const boost::property_tree::ptree& prm, bool useKamg = false) { auto crit = amgCriterion(prm); auto sargs = amgSmootherArgs(prm); if(useKamg){ return std::make_shared< Dune::DummyUpdatePreconditioner< Dune::Amg::KAMG< Operator, Vector, Smoother> > >(op, crit, sargs, prm.get("max_krylov", 1), prm.get("min_reduction", 1e-1) ); }else{ return std::make_shared>(op, crit, sargs); } } /// Helper method to determine if the local partitioning has the /// K interior cells from [0, K-1] and ghost cells from [K, N-1]. /// Returns K if true, otherwise returns N. This is motivated by /// usage in the ParallelOverlappingILU0 preconditiner. template static size_t interiorIfGhostLast(const CommArg& comm) { size_t interior_count = 0; size_t highest_interior_index = 0; const auto& is = comm.indexSet(); for (const auto& ind : is) { if (CommArg::OwnerSet::contains(ind.local().attribute())) { ++interior_count; highest_interior_index = std::max(highest_interior_index, ind.local().local()); } } if (highest_interior_index + 1 == interior_count) { return interior_count; } else { return is.size(); } } static PrecPtr createParILU(const Operator& op, const boost::property_tree::ptree& prm, const Comm& comm, const int ilulevel) { const double w = prm.get("relaxation", 1.0); const bool redblack = prm.get("redblack", false); const bool reorder_spheres = prm.get("reorder_spheres", false); // Already a parallel preconditioner. Need to pass comm, but no need to wrap it in a BlockPreconditioner. if (ilulevel == 0) { const size_t num_interior = interiorIfGhostLast(comm); return std::make_shared>( op.getmat(), comm, w, Opm::MILU_VARIANT::ILU, num_interior, redblack, reorder_spheres); } else { return std::make_shared>( op.getmat(), comm, ilulevel, w, Opm::MILU_VARIANT::ILU, redblack, reorder_spheres); } } // Add a useful default set of preconditioners to the factory. // This is the default template, used for parallel preconditioners. // (Serial specialization below). template void addStandardPreconditioners(const CommArg*) { using namespace Dune; using O = Operator; using M = Matrix; using V = Vector; using P = boost::property_tree::ptree; using C = Comm; doAddCreator("ILU0", [](const O& op, const P& prm, const std::function&, const C& comm) { return createParILU(op, prm, comm, 0); }); doAddCreator("ParOverILU0", [](const O& op, const P& prm, const std::function&, const C& comm) { return createParILU(op, prm, comm, prm.get("ilulevel", 0)); }); doAddCreator("ILUn", [](const O& op, const P& prm, const std::function&, const C& comm) { return createParILU(op, prm, comm, prm.get("ilulevel", 0)); }); doAddCreator("Jac", [](const O& op, const P& prm, const std::function&, const C& comm) { const int n = prm.get("repeats", 1); const double w = prm.get("relaxation", 1.0); return wrapBlockPreconditioner>>(comm, op.getmat(), n, w); }); doAddCreator("GS", [](const O& op, const P& prm, const std::function&, const C& comm) { const int n = prm.get("repeats", 1); const double w = prm.get("relaxation", 1.0); return wrapBlockPreconditioner>>(comm, op.getmat(), n, w); }); doAddCreator("SOR", [](const O& op, const P& prm, const std::function&, const C& comm) { const int n = prm.get("repeats", 1); const double w = prm.get("relaxation", 1.0); return wrapBlockPreconditioner>>(comm, op.getmat(), n, w); }); doAddCreator("SSOR", [](const O& op, const P& prm, const std::function&, const C& comm) { const int n = prm.get("repeats", 1); const double w = prm.get("relaxation", 1.0); return wrapBlockPreconditioner>>(comm, op.getmat(), n, w); }); // Only add AMG preconditioners to the factory if the operator // is the overlapping schwarz operator. This could be extended // later, but at this point no other operators are compatible // with the AMG hierarchy construction. if constexpr (std::is_same_v>) { doAddCreator("amg", [](const O& op, const P& prm, const std::function&, const C& comm) { const std::string smoother = prm.get("smoother", "ParOverILU0"); if (smoother == "ILU0" || smoother == "ParOverILU0") { using Smoother = Opm::ParallelOverlappingILU0; auto crit = amgCriterion(prm); auto sargs = amgSmootherArgs(prm); return std::make_shared>(op, crit, sargs, comm); } else { OPM_THROW(std::invalid_argument, "Properties: No smoother with name " << smoother << "."); } }); } doAddCreator("cpr", [](const O& op, const P& prm, const std::function weightsCalculator, const C& comm) { assert(weightsCalculator); return std::make_shared>(op, prm, weightsCalculator, comm); }); doAddCreator("cprt", [](const O& op, const P& prm, const std::function weightsCalculator, const C& comm) { assert(weightsCalculator); return std::make_shared>(op, prm, weightsCalculator, comm); }); } // Add a useful default set of preconditioners to the factory. // This is the specialization for the serial case. void addStandardPreconditioners(const Dune::Amg::SequentialInformation*) { using namespace Dune; using O = Operator; using M = Matrix; using V = Vector; using P = boost::property_tree::ptree; doAddCreator("ILU0", [](const O& op, const P& prm, const std::function&) { const double w = prm.get("relaxation", 1.0); return std::make_shared>( op.getmat(), 0, w, Opm::MILU_VARIANT::ILU); }); doAddCreator("ParOverILU0", [](const O& op, const P& prm, const std::function&) { const double w = prm.get("relaxation", 1.0); const int n = prm.get("ilulevel", 0); return std::make_shared>( op.getmat(), n, w, Opm::MILU_VARIANT::ILU); }); doAddCreator("ILUn", [](const O& op, const P& prm, const std::function&) { const int n = prm.get("ilulevel", 0); const double w = prm.get("relaxation", 1.0); return std::make_shared>( op.getmat(), n, w, Opm::MILU_VARIANT::ILU); }); doAddCreator("Jac", [](const O& op, const P& prm, const std::function&) { const int n = prm.get("repeats", 1); const double w = prm.get("relaxation", 1.0); return wrapPreconditioner>(op.getmat(), n, w); }); doAddCreator("GS", [](const O& op, const P& prm, const std::function&) { const int n = prm.get("repeats", 1); const double w = prm.get("relaxation", 1.0); return wrapPreconditioner>(op.getmat(), n, w); }); doAddCreator("SOR", [](const O& op, const P& prm, const std::function&) { const int n = prm.get("repeats", 1); const double w = prm.get("relaxation", 1.0); return wrapPreconditioner>(op.getmat(), n, w); }); doAddCreator("SSOR", [](const O& op, const P& prm, const std::function&) { const int n = prm.get("repeats", 1); const double w = prm.get("relaxation", 1.0); return wrapPreconditioner>(op.getmat(), n, w); }); // Only add AMG preconditioners to the factory if the operator // is an actual matrix operator. if constexpr (std::is_same_v>) { doAddCreator("amg", [](const O& op, const P& prm, const std::function&) { const std::string smoother = prm.get("smoother", "ParOverILU0"); if (smoother == "ILU0" || smoother == "ParOverILU0") { #if DUNE_VERSION_NEWER(DUNE_ISTL, 2, 7) using Smoother = SeqILU; #else using Smoother = SeqILU0; #endif return makeAmgPreconditioner(op, prm); } else if (smoother == "Jac") { using Smoother = SeqJac; return makeAmgPreconditioner(op, prm); } else if (smoother == "SOR") { using Smoother = SeqSOR; return makeAmgPreconditioner(op, prm); } else if (smoother == "SSOR") { using Smoother = SeqSSOR; return makeAmgPreconditioner(op, prm); } else if (smoother == "ILUn") { #if DUNE_VERSION_NEWER(DUNE_ISTL, 2, 7) using Smoother = SeqILU; #else using Smoother = SeqILUn; #endif return makeAmgPreconditioner(op, prm); } else { OPM_THROW(std::invalid_argument, "Properties: No smoother with name " << smoother << "."); } }); doAddCreator("kamg", [](const O& op, const P& prm, const std::function&) { const std::string smoother = prm.get("smoother", "ParOverILU0"); if (smoother == "ILU0" || smoother == "ParOverILU0") { #if DUNE_VERSION_NEWER(DUNE_ISTL, 2, 7) using Smoother = SeqILU; #else using Smoother = SeqILU0; #endif return makeAmgPreconditioner(op, prm, true); } else if (smoother == "Jac") { using Smoother = SeqJac; return makeAmgPreconditioner(op, prm, true); } else if (smoother == "SOR") { using Smoother = SeqSOR; return makeAmgPreconditioner(op, prm, true); // } else if (smoother == "GS") { // using Smoother = SeqGS; // return makeAmgPreconditioner(op, prm, true); } else if (smoother == "SSOR") { using Smoother = SeqSSOR; return makeAmgPreconditioner(op, prm, true); } else if (smoother == "ILUn") { #if DUNE_VERSION_NEWER(DUNE_ISTL, 2, 7) using Smoother = SeqILU; #else using Smoother = SeqILUn; #endif return makeAmgPreconditioner(op, prm, true); } else { OPM_THROW(std::invalid_argument, "Properties: No smoother with name " << smoother << "."); } }); doAddCreator("famg", [](const O& op, const P& prm, const std::function&) { auto crit = amgCriterion(prm); Dune::Amg::Parameters parms; parms.setNoPreSmoothSteps(1); parms.setNoPostSmoothSteps(1); return wrapPreconditioner>(op, crit, parms); }); } doAddCreator("cpr", [](const O& op, const P& prm, const std::function& weightsCalculator) { return std::make_shared>(op, prm, weightsCalculator); }); doAddCreator("cprt", [](const O& op, const P& prm, const std::function& weightsCalculator) { return std::make_shared>(op, prm, weightsCalculator); }); } // The method that implements the singleton pattern, // using the Meyers singleton technique. static PreconditionerFactory& instance() { static PreconditionerFactory singleton; return singleton; } // Private constructor, to keep users from creating a PreconditionerFactory. PreconditionerFactory() { Comm* dummy = nullptr; addStandardPreconditioners(dummy); } // Actually creates the product object. PrecPtr doCreate(const Operator& op, const boost::property_tree::ptree& prm, const std::function weightsCalculator) { const std::string& type = prm.get("type", "ParOverILU0"); auto it = creators_.find(type); if (it == creators_.end()) { std::ostringstream msg; msg << "Preconditioner type " << type << " is not registered in the factory. Available types are: "; for (const auto& prec : creators_) { msg << prec.first << ' '; } msg << std::endl; OPM_THROW(std::invalid_argument, msg.str()); } return it->second(op, prm, weightsCalculator); } PrecPtr doCreate(const Operator& op, const boost::property_tree::ptree& prm, const std::function weightsCalculator, const Comm& comm) { const std::string& type = prm.get("type", "ParOverILU0"); auto it = parallel_creators_.find(type); if (it == parallel_creators_.end()) { std::ostringstream msg; msg << "Parallel preconditioner type " << type << " is not registered in the factory. Available types are: "; for (const auto& prec : parallel_creators_) { msg << prec.first << ' '; } msg << std::endl; OPM_THROW(std::invalid_argument, msg.str()); } return it->second(op, prm, weightsCalculator, comm); } // Actually adds the creator. void doAddCreator(const std::string& type, Creator c) { creators_[type] = c; } // Actually adds the creator. void doAddCreator(const std::string& type, ParCreator c) { parallel_creators_[type] = c; } // This map contains the whole factory, i.e. all the Creators. std::map creators_; std::map parallel_creators_; }; } // namespace Dune #endif // OPM_PRECONDITIONERFACTORY_HEADER