Using MueLu in User Applications
This tutorial demonstrates how to use MueLu from within user applications in C++.
We will use the Tpetra linear algebra stack.
We will read the configuration for MueLu from an XML file and then create a MueLu::Hierarchy.
This will then be used as a preconditioner within Belos as well as a standalone solver.
Note
There is also support for Stratimikos.
Please refer to the examples in the MueLu folder for more details.
XML Interface using CreateTpetraPreconditioner
The most comfortable way to declare the multigrid parameters for MueLu is using the XML interface. In fact, MueLu provides two different XML interfaces. There is a simplified XML interface for multigrid users and a more advanced XML interface for experts, which allows to make use of all features of MueLu as a multigrid framework. Both XML file formats are introduced in the previous sections of this hands on tutorial. However, for the C++ code it makes no difference which type of XML interface is used.
Note
In the next sections, we give some code snippets.
THey are borrowed form the TutorialDriver.cpp file in the tutorial tests.
Preparations
First of all, we need to grab a communicator object. Therefore, it is easy to use some utilities from the Teuchos package:
RCP<const Teuchos::Comm<int>> comm = Teuchos::DefaultComm<int>::getComm();
[[maybe_unused]] int MyPID = comm->getRank();
[[maybe_unused]] int NumProc = comm->getSize();
For the multigrid method, we need a linear operator \(A\). We can generate a problem-dependent matrix operator using the Galeri package (see Example problem), with Tpetra as our underlying linear algebra framework (through Xpetra wrappers). Below is an example for the Laplace2D Problem:
// Create the matrix
RCP<Galeri::Xpetra::Problem<Map, CrsMatrixWrap, MultiVector>> galeriProblem =
Galeri::Xpetra::BuildProblem<SC, LO, GO, Map, CrsMatrixWrap, MultiVector>("Laplace2D", xpetra_map, galeriList);
RCP<Matrix> A = galeriProblem->BuildMatrix();
For aggregation-based algebraic multigrid methods, one has to provide a valid set of near null space vectors to produce transfer operators. In case of a Laplace problem, we just use a constant vector.
// Build null space vector for a scalar problem, i.e. vector with all ones
RCP<MultiVector> nullspace = rcp(new MultiVector(dofMap, 1));
nullspace->putScalar(one);
Setup phase
With a fine level operator \(A\) available as Tpetra::CrsMatrix object
and a set of near null space vectors (available as Tpetra::MultiVector),
all minimum requirements are fulfilled for generating an algebraic multigrid hierarchy.
There are two different ways to setup a multigrid hierarchy in MueLu.
One can either use a parameter list driven setup process which accepts either Teuchos::ParameterList objects
or XML files in two different XML file formats.
Alternatively, one can use the MueLu C++ API to define the multigrid setup at compile time.
In the next sections we show both variants.
The most comfortable way to declare the multigrid parameters for MueLu is using the XML interface.
In fact, MueLu provides two different XML interfaces.
There is a simplified XML interface for multigrid users and a more advanced XML interface for expert which allows to make use of all features of MueLu as a multigrid framework.
Both XML file formats are introduced in the previous sections of this hands on tutorial.
However, for the C++ code it makes no difference which type of XML interface is used.
We first read the MueLu configuration from the XML file:
// Read MueLu parameter list from xml file
RCP<ParameterList> mueluParams = Teuchos::getParametersFromXmlFile(xmlFileName);
Then, we store the near null space vectors in the "user data" sublist of this parameter list:
// Register nullspace as user data in the MueLu parameter list
ParameterList& userDataList = mueluParams->sublist("user data");
userDataList.set<RCP<MultiVector>>("Nullspace", nullspace);
We then can create a MueLu object ready to be used as a preconditioner:
// Create the MueLu preconditioner based on the Tpetra stack
RCP<MueLu::TpetraOperator<SC, LO, GO, NO>> mueLuPreconditioner =
MueLu::CreateTpetraPreconditioner(Teuchos::rcp_dynamic_cast<Operator>(matrix), *mueluParams);
Iteration Phase
Once the setup phase is completed, the MueLu multigrid hierarchy is ready for being used.
There are several ways how to use the multigrid method. One can apply the multigrid method as standalone solver for linear systems. Multigrid methods are also known to be efficient preconditioners within iterative (Krylov) solvers such as CG or GMRES methods.
In the next subsections it is demonstrated how to use MueLu as standalone solver and as preconditioner for iterative solvers from the Belos and AztecOO package in Trilinos.
For solving a linear system \(Ax=b\), we need a right hand side vector \(b\). When using iterative solvers we also need an initial guess for the solution vector.
In this example we just create Tpetra::MultiVectors (with just a single vector each).
The right-hand side vector \(b\) is initialized with ones and the solution vector \(x\) is filled with random values.
MueLu as multigrid solver
MueLu can be used as standalone solver. First, we create and initialize a solution vector:
// Create solution vector and set it to an initial guess (X0)
RCP<MultiVector> multigridSolVec = rcp(new MultiVector(dofMap, 1, false));
Tpetra::deep_copy(*multigridSolVec, *X0);
If necessary, extract the MueLu::Hiearchy from the Tpetra preconditioner object:
// Extract the underlying MueLu hierarchy
RCP<MueLu::Hierarchy<SC, LO, GO, NO>> hierarchy = mueLuPreconditioner->GetHierarchy();
Then, the MueLu::Hierarchy object is set to the non-preconditioner mode:
// Configure MueLu to be used as solver
hierarchy->IsPreconditioner(false);
Finally, we solve the system by calling the Iterate() routine
to perform mgridSweeps sweeps with the chosen multigrid cycle.
// Solve
hierarchy->Iterate(*Xpetra::toXpetra(B), *Xpetra::toXpetra(multigridSolVec), mgridSweeps);
If successful, the multigridSolVec vector contains the solution.
MueLu as preconditioner for Belos
Belos is the Krylov package in Trilinos and works both for Epetra and Tpetra. Here, we demonstrate how to use MueLu as preconditioner for Belos solvers using Tpetra.
First, we create and initialize a solution vector:
// Create solution vector and set it to an initial guess (X0)
RCP<MultiVector> precSolVec = rcp(new MultiVector(dofMap, 1, false));
Tpetra::deep_copy(*precSolVec, *X0);
Then, we create a Belos::LinearProblem and hand in the MueLu preconditioner object:
// Construct a Belos LinearProblem object and hand-in the MueLu preconditioner
RCP<Belos::LinearProblem<SC, MultiVector, Operator>> belosProblem =
rcp(new Belos::LinearProblem<SC, MultiVector, Operator>(matrix, precSolVec, B));
belosProblem->setLeftPrec(mueLuPreconditioner);
bool set = belosProblem->setProblem();
Now, we define the linear solver configuration in a Teuchos::ParameterList
and create the Belos solver with this configuration:
// Belos parameter list
RCP<ParameterList> belosList = Teuchos::parameterList();
belosList->set("Maximum Iterations", 500); // Maximum number of iterations allowed
belosList->set("Convergence Tolerance", tol); // Relative convergence tolerance requested
belosList->set("Verbosity", Belos::Errors + Belos::Warnings + Belos::StatusTestDetails);
belosList->set("Output Frequency", 1);
belosList->set("Output Style", Belos::Brief);
// Create an iterative solver manager
Belos::SolverFactory<SC, MultiVector, Operator> solverFactory;
RCP<Belos::SolverManager<SC, MultiVector, Operator>> solver;
if (dsolveType == "gmres")
solver = solverFactory.create("Block GMRES", belosList);
else if (dsolveType == "cg")
solver = solverFactory.create("CG", belosList);
else {
fancyout << "\nERROR: Please select a valid solver!" << std::endl;
return EXIT_FAILURE;
}
// Pass the linear problem to the solver
solver->setProblem(belosProblem);
Finally, we solve the linear system:
// Perform solve
Belos::ReturnType retStatus = Belos::Unconverged;
retStatus = solver->solve();
Full example using the XML interface
The reader may refer to TutorialDriver.cpp for a working example to study the source code.
This demonstration program has some more features that are not discussed in this tutorial.
Exercise 1
Compile the example in TutorialDriver.cpp and then run the program in parallel using two processors
mpirun -np 2 ./MueLu_TutorialDriver.exe --help
Study the screen output and try to run the example with an XML file as input for the multigrid setup.
Exercise 2
Create large scale examples using the
--nxand--nyparameters for a finer mesh.
Choose reasonable numbers for --nx and --ny for your machine
and make use of your knowledge about MueLu for generating efficient preconditioners.
C++ Interface
As an alternative to the XML interfaces, the user can also define the multigrid hierarchy using the C++ API directly.
In contrary to the XML interface, which allows to build the layout of the multigrid preconditioner at runtime,
the preconditioner is fully defined at compile time when using the C++ interface.
First, a MueLu::Hierarchy object has to be defined, which manages the multigrid hierarchy including all multigrid levels.
It provides routines for the multigrid setup and the multigrid cycle algorithms (such as V-cycle and W-cycle).
// create new hierarchy
RCP<MueLu::Hierarchy<SC, LO, GO, NO>> H;
There are some member functions which can be used to describe the basic multigrid hierarchy.
The SetMaxCoarseSize member function is used to set the maximum size of the coarse level problem before the coarsening process can be stopped.
// Instantiate new Hierarchy object
H = rcp(new Hierarchy());
H->setDefaultVerbLevel(Teuchos::VERB_HIGH);
H->SetMaxCoarseSize((GO)50);
Next, one defines an empty MueLu::Level object for the finest level.
The MueLu::Level objects represent a data container storing the internal variables on each multigrid level.
The user has to provide and fill the level container for the finest level only.
The MueLu::Hierarchy object then automatically generates the coarse levels using the multigrid parameters.
The absolute minimum requirements for the finest level that the user has to provide is the fine level operator \(A\) which represents the fine level matrix.
MueLu is based on Xpetra. So, the matrix \(A\) has to be of type Xpetra::Matrix.
In addition, the user should also provide a valid set of near null space vectors.
For a Laplace problem we can just use the constant nullspace vector that has previously been defined.
Some routines need additional information.
For example, the user has to provide the node coordinates for repartitioning.
// create a fine level object
RCP<Level> Finest = H->GetLevel();
Finest->setDefaultVerbLevel(Teuchos::VERB_HIGH);
Finest->Set("A", A);
Finest->Set("Nullspace", nullspace);
Finest->Set("Coordinates", coordinates);
Note
When including the
MueLu_UseShortNames.hppheader file,
the template parameters usually can be dropped for compiling.
The most important template parameters are SC for the scalar type,
LO for the local ordinal type and GO for the global ordinal type.
For a detailed description of the template parameters,
the reader may refer to the Tpetra documentation.
A MueLu::FactoryManager object is used for the internal management of data dependencies and generating algorithms of the multigrid setup.
Even though not absolutely necessary,
we show the usage of the MueLu::FactoryManager object as it allows for user-specific enhancements of the multigrid code.
MueLu::FactoryManager M;
M.SetKokkosRefactor(false);
The user can define its own factories for performing different tasks in the setup process.
The following code shows how to define a smoothed aggregation transfer operator and a restriction operator.
The MueLu::RAPFactory is used for the (standard) Galerkin product to generate the coarse level matrix \(A\).
// declare some factories (potentially overwrite default factories)
RCP<SaPFactory> PFact = rcp(new SaPFactory());
PFact->SetParameter("sa: damping factor", Teuchos::ParameterEntry(4. / 3));
RCP<Factory> RFact = rcp(new TransPFactory());
RCP<RAPFactory> AcFact = rcp(new RAPFactory());
AcFact->setVerbLevel(Teuchos::VERB_HIGH);
H->SetImplicitTranspose(true);
Teuchos::ParameterList Aclist = *(AcFact->GetValidParameterList());
Aclist.set("transpose: use implicit", true);
AcFact->SetParameterList(Aclist);
The user-defined factories have to be registered in the FactoryManager using the lines
// configure factory manager (no repartitioning)
M.SetFactory("P", PFact);
M.SetFactory("R", RFact);
M.SetFactory("A", AcFact);
Warning
If you forget to register the new factories, the
FactoryManagerwill use some internal default factories for being responsible to create the corresponding variables.
Then your user-specified factories are just ignored during the multigrid setup!
Note
The
FactoryManageris also responsible for resolving all dependencies between different factories.
That is, after the user-defined factories have been registered,
all factories that request variable \(P\) are provided with the prolongation operator \(P\) that has been generated by the registered factory PFact.
If there is some data requested for which no factory has been registered by the user,
the FactoryManager manages an internal list for reasonable default choices and default factories.
Next, the user has to declare a level smoother. The following code can be used to define a symmetric Gauss-Seidel smoother. Other methods can be set up in a similar way.
// define smoother object
std::string ifpackType;
Teuchos::ParameterList ifpackList;
ifpackList.set("relaxation: sweeps", (LO)2);
ifpackList.set("relaxation: damping factor", (SC)1.0);
ifpackType = "RELAXATION";
ifpackList.set("relaxation: type", "Symmetric Gauss-Seidel");
Before the level smoother can be used, a MueLu::SmootherFactory has to be defined for the smoother factory.
The SmootherFactory is used in the multigrid setup to generate level smoothers for the corresponding levels using the prototyping design pattern.
Note, that the SmootherFactory has also to be registered in the FactoryManager object.
If the user forgets this, the multigrid setup will use some kind of default smoother, i.e., the user-chosen smoother options are just ignored.
// create smoother factory
RCP<SmootherPrototype> smootherPrototype = rcp(new TrilinosSmoother(ifpackType, ifpackList));
M.SetFactory("Smoother", rcp(new SmootherFactory(smootherPrototype)));
Once the FactoryManager is set up, it can be used with the Hierarchy::Setup routine to initiate the coarsening process and set up the multigrid hierarchy.
// setup multigrid hierarchy
int startLevel = 0;
H->Setup(M, startLevel, 10);