-
Notifications
You must be signed in to change notification settings - Fork 1
NOXNewton2
sjdeal edited this page Jul 22, 2015
·
1 revision
//
// Simple example of solving the following nonlinear partial
// differential equation (PDE):
//
// -\Delta u + \lambda e^u = 0 in \Omega = (0,1) \times (0,1)
// u = 0 on \partial \Omega
//
// using NOX (Trilinos' Nonlinear Object-Oriented Solutions package).
// For more details and documentation, see the NOX web site:
//
// http://trilinos.sandia.gov/packages/nox/
//
#ifdef HAVE_MPI
# include "mpi.h"
# include "Epetra_MpiComm.h"
#else
# include "Epetra_SerialComm.h"
#endif
#include "Epetra_Map.h"
#include "Epetra_CrsMatrix.h"
#include "Epetra_RowMatrix.h"
#include "Epetra_Vector.h"
#include "NOX.H"
#include "NOX_Epetra_Interface_Required.H"
#include "NOX_Epetra_Interface_Jacobian.H"
#include "NOX_Epetra_LinearSystem_AztecOO.H"
#include "NOX_Epetra_Group.H"
//
// Report the the number of lower, upper, left and right nodes, for
// each element of a Cartesian grid containing nx x ny elements.
//
static void
getNeighbors (const int i, const int nx, const int ny,
int & left, int & right,
int & lower, int & upper)
{
int ix, iy;
ix = i % nx;
iy = (i - ix) / nx;
if( ix == 0 )
left = -1;
else
left = i-1;
if( ix == nx-1 )
right = -1;
else
right = i+1;
if( iy == 0 )
lower = -1;
else
lower = i-nx;
if( iy == ny-1 )
upper = -1;
else
upper = i+nx;
return;
}
//
// Return a new sparse matrix corresponding to the discretization of a
// Laplacian over a 2-D Cartesian grid, with nx grid points along the
// x axis and ny grid points along the y axis. For simplicity's sake,
// we assume that all nodes in the grid are internal (Dirichlet
// boundary nodes should alread have been condensated).
//
// Input arguments:
//
// nx: Number of (internal) grid points along the x axis.
// ny: Number of (internal) grid points along the y axis.
// Comm: The communicator over which to distribute the returned sparse
// matrix.
//
// Return: The sparse matrix, distributed over the given communicator.
//
Teuchos::RCP<Epetra_CrsMatrix>
CreateLaplacian (const int nx,
const int ny,
const Epetra_Comm& Comm)
{
using Teuchos::RCP;
using Teuchos::rcp;
int NumGlobalElements = nx * ny;
// Create a map that distributes the elements evenly over the
// process(es) participating in the given communicator.
Epetra_Map Map (NumGlobalElements, 0, Comm);
// Local number of rows
int NumMyElements = Map.NumMyElements();
// Get the list of the global elements that belong to my MPI process.
int* MyGlobalElements = Map.MyGlobalElements();
// Grid
double hx = 1.0/(nx-1);
double hy = 1.0/(ny-1);
double off_left = -1.0/(hx*hx);
double off_right = -1.0/(hx*hx);
double off_lower = -1.0/(hy*hy);
double off_upper = -1.0/(hy*hy);
double diag = 2.0/(hx*hx) + 2.0/(hy*hy);
int left, right, lower, upper;
// "5" is a conservative (slightly too large in some cases)
// overestimate of the number of nonzero elements per row of the
// sparse matrix. The estimate doesn't have to be exact since we
// are using Epetra_CrsMatrix in dynamic allocation mode.
RCP<Epetra_CrsMatrix> A = rcp (new Epetra_CrsMatrix (Copy, Map, 5));
// Add rows one at a time to the sparse matrix.
std::vector<double> Values (4);
std::vector<int> Indices (4);
for (int i = 0; i < NumMyElements; ++i)
{
int NumEntries = 0;
getNeighbors (MyGlobalElements[i], nx, ny,
left, right, lower, upper);
if (left != -1) {
Indices[NumEntries] = left;
Values[NumEntries] = off_left;
++NumEntries;
}
if (right != -1) {
Indices[NumEntries] = right;
Values[NumEntries] = off_right;
++NumEntries;
}
if (lower != -1) {
Indices[NumEntries] = lower;
Values[NumEntries] = off_lower;
++NumEntries;
}
if (upper != -1) {
Indices[NumEntries] = upper;
Values[NumEntries] = off_upper;
++NumEntries;
}
// Insert the off-diagonal entries first. This is not required
// for CrsMatrix, but it is apparently an old habit for many
// Aztec users.
A->InsertGlobalValues (MyGlobalElements[i], NumEntries, &Values[0], &Indices[0]);
// Insert the diagonal entry.
A->InsertGlobalValues (MyGlobalElements[i], 1, &diag, MyGlobalElements+i);
}
// Let Epetra reorganize the matrix and compute the global ordering.
A->FillComplete();
return A;
}
// ==========================================================================
// The PDEProblem class defines the nonlinear problem to solve.
// It provides two methods:
//
// ComputeF: computes F(x) for a given Epetra_Vector x
//
// UpdateJacobian: updates the entries of the Jacobian matrix.
//
// The Jacobian matrix in this case can be written as
//
// J = L + diag(lambda*exp(x[i])),
//
// where L corresponds to the discretization of a Laplacian, and diag
// is a diagonal matrix with entries lambda*exp(x[i]). Thus, to
// update the jacobian we simply update the diagonal entries, by
// supplying x as a vector. Similarly, to compute F(x), we reset J to
// be equal to L, multiply it by the (distributed) vector x, and then
// add the diagonal contribution.
// ==========================================================================
class PDEProblem {
public:
//
// Constructor.
//
// Input arguments:
//
// nx: Number of (internal) elements along the x axis.
// ny: Number of (internal) elements along the y axis.
// lambda: Scaling parameter of the Jacobian matrix.
// Comm: Communicator over which to distribute the matrix.
//
PDEProblem (const int nx,
const int ny,
const double lambda,
const Epetra_Comm& Comm) :
nx_(nx), ny_(ny), lambda_(lambda)
{
hx_ = 1.0/(nx_-1);
hy_ = 1.0/(ny_-1);
Matrix_ = CreateLaplacian (nx_,ny_, Comm);
}
// The destructor doesn't need to do anything. RCPs are smart
// pointers; they handle deallocation automatically.
~PDEProblem() {}
// Compute F(x).
void
ComputeF (const Epetra_Vector& x, Epetra_Vector& f)
{
// Reset the diagonal entries.
double diag = 2.0/(hx_*hx_) + 2.0/(hy_*hy_);
int NumMyElements = Matrix_->Map().NumMyElements();
// Get the list of the global elements that belong to my MPI process.
int* MyGlobalElements = Matrix_->Map ().MyGlobalElements ();
for (int i = 0; i < NumMyElements; ++i) {
// Update the diagonal entry of the matrix.
Matrix_->ReplaceGlobalValues (MyGlobalElements[i], 1, &diag, MyGlobalElements+i);
}
// Sparse matrix-vector product.
// Interprocess communication happens here.
Matrix_->Multiply (false, x, f);
for (int i = 0; i < NumMyElements; ++i) {
// Include the contribution from the current diagonal entry.
f[i] += lambda_*exp(x[i]);
}
}
// Update the Jacobian matrix for a given vector x (see class
// documentation for an explanation of how x contributes to the
// update formula).
void
UpdateJacobian (const Epetra_Vector & x)
{
double diag = 2.0/(hx_*hx_) + 2.0/(hy_*hy_);
int NumMyElements = Matrix_->Map ().NumMyElements ();
// Get the list of the global elements that belong to my MPI process.
int* MyGlobalElements = Matrix_->Map ().MyGlobalElements ();
for (int i = 0; i < NumMyElements; ++i) {
// Update the current diagonal entry.
double newdiag = diag + lambda_*exp(x[i]);
Matrix_->ReplaceGlobalValues (MyGlobalElements[i], 1,
&newdiag, MyGlobalElements+i);
}
}
// Return a pointer to the internally stored matrix.
Teuchos::RCP<Epetra_CrsMatrix> GetMatrix() {
return Matrix_;
}
private:
int nx_, ny_;
double hx_, hy_;
Teuchos::RCP<Epetra_CrsMatrix> Matrix_;
double lambda_;
};
// ==========================================================================
// SimpleProblemInterface defines the interface between NOX and our
// nonlinear problem to solve. Its constructor accepts a PDEProblem
// object, which it uses to update the Jacobian and compute F(x).
// This interface is a bit crude; for example, it does not provide a
// PrecMatrix or Preconditioner.
// ==========================================================================
class SimpleProblemInterface :
public NOX::Epetra::Interface::Required,
public NOX::Epetra::Interface::Jacobian
{
public:
// The constructor takes a PDEProblem pointer.
SimpleProblemInterface (Teuchos::RCP<PDEProblem> Problem) :
Problem_(Problem)
{}
// The destructor doesn't need to do anything, because RCPs are
// smart pointers; they handle deallocation automatically.
~SimpleProblemInterface() {}
bool
computeF (const Epetra_Vector& x,
Epetra_Vector& f,
NOX::Epetra::Interface::Required::FillType F)
{
Problem_->ComputeF (x, f);
return true;
}
bool
computeJacobian (const Epetra_Vector& x, Epetra_Operator& Jac)
{
Problem_->UpdateJacobian (x);
return true;
}
bool
computePrecMatrix (const Epetra_Vector& x, Epetra_RowMatrix& M)
{
throw std::runtime_error ("*** SimpleProblemInterface does not implement "
"computing an explicit preconditioner from an "
"Epetra_RowMatrix ***");
}
bool
computePreconditioner(const Epetra_Vector & x, Epetra_Operator & O)
{
throw std::runtime_error ("*** SimpleProblemInterface does not implement "
"computing an explicit preconditioner from an "
"Epetra_Operator ***");
}
private:
Teuchos::RCP<PDEProblem> Problem_;
};
//
// Test driver routine.
//
int
main (int argc, char **argv)
{
using Teuchos::ParameterList;
using Teuchos::parameterList;
using Teuchos::RCP;
using Teuchos::rcp;
using std::cout;
using std::endl;
#ifdef HAVE_MPI
MPI_Init(&argc, &argv);
Epetra_MpiComm Comm(MPI_COMM_WORLD);
#else
Epetra_SerialComm Comm;
#endif
// Parameters for setting up the nonlinear PDE. The 2-D regular
// mesh on which the PDE's discretization is defined is nx by ny
// (internal nodes; we assume Dirichlet boundary conditions have
// been condensed out).
const int nx = 5;
const int ny = 6;
const double lambda = 1.0;
RCP<PDEProblem> Problem = rcp (new PDEProblem (nx, ny, lambda, Comm));
// Prepare the initial guess vector. It should be a vector in the
// domain of the nonlinear problem's matrix.
Epetra_Vector InitialGuess (Problem->GetMatrix ()->OperatorDomainMap ());
// Make the starting solution a zero vector.
InitialGuess.PutScalar(0.0);
// Set up the problem interface.
RCP<SimpleProblemInterface> interface =
rcp (new SimpleProblemInterface (Problem));
// Create the top-level parameter list to control NOX.
//
// "parameterList" (lowercase initial "p") is a "nonmember
// constructor" that returns an RCP<ParameterList> with the
// given name.
RCP<ParameterList> params = parameterList ("NOX");
// Tell the nonlinear solver to use line search.
params->set ("Nonlinear Solver", "Line Search Based");
//
// Set the printing parameters in the "Printing" sublist.
//
ParameterList& printParams = params->sublist ("Printing");
printParams.set ("MyPID", Comm.MyPID ());
printParams.set ("Output Precision", 3);
printParams.set ("Output Processor", 0);
printParams.set ("Output Information",
NOX::Utils::OuterIteration +
NOX::Utils::OuterIterationStatusTest +
NOX::Utils::InnerIteration +
NOX::Utils::Parameters +
NOX::Utils::Details +
NOX::Utils::Warning);
//
// Set the nonlinear solver parameters.
//
// Line search parameters.
ParameterList& searchParams = params->sublist ("Line Search");
searchParams.set ("Method", "More'-Thuente");
// Parameters for picking the search direction.
Teuchos::ParameterList& dirParams = params->sublist ("Direction");
// Use Newton's method to pick the search direction.
dirParams.set ("Method", "Newton");
// Parameters for Newton's method.
ParameterList& newtonParams = dirParams.sublist ("Newton");
newtonParams.set ("Forcing Term Method", "Constant");
//
// Newton's method invokes a linear solver repeatedly.
// Set the parameters for the linear solver.
//
ParameterList& lsParams = newtonParams.sublist ("Linear Solver");
// Use Aztec's implementation of GMRES, with at most 800 iterations,
// a residual tolerance of 1.0e-4, with output every 50 iterations,
// and Aztec's native ILU preconditioner.
lsParams.set ("Aztec Solver", "GMRES");
lsParams.set ("Max Iterations", 800);
lsParams.set ("Tolerance", 1e-4);
lsParams.set ("Output Frequency", 50);
lsParams.set ("Aztec Preconditioner", "ilu");
RCP<Epetra_CrsMatrix> A = Problem->GetMatrix();
// Our SimpleProblemInterface implements both Required and
// Jacobian, so we can use the same object for each.
RCP<NOX::Epetra::Interface::Required> iReq = interface;
RCP<NOX::Epetra::Interface::Jacobian> iJac = interface;
RCP<NOX::Epetra::LinearSystemAztecOO> linSys =
rcp (new NOX::Epetra::LinearSystemAztecOO (printParams, lsParams,
iReq, iJac, A, InitialGuess));
// Need a NOX::Epetra::Vector for constructor.
NOX::Epetra::Vector noxInitGuess (InitialGuess, NOX::DeepCopy);
RCP<NOX::Epetra::Group> group =
rcp (new NOX::Epetra::Group (printParams, iReq, noxInitGuess, linSys));
//
// Set up NOX's iteration stopping criteria ("status tests").
//
// ||F(X)||_2 / N < 1.0e-4, where N is the length of F(X).
//
// NormF has many options for setting up absolute vs. relative
// (scaled by the norm of the initial guess) tolerances, scaling or
// not scaling by the length of F(X), and choosing a different norm
// (we use the 2-norm here).
RCP<NOX::StatusTest::NormF> testNormF =
rcp (new NOX::StatusTest::NormF (1.0e-4));
// At most 20 (nonlinear) iterations.
RCP<NOX::StatusTest::MaxIters> testMaxIters =
rcp (new NOX::StatusTest::MaxIters (20));
// Combine the above two stopping criteria (normwise convergence,
// and maximum number of nonlinear iterations). The result tells
// NOX to stop if at least one of them is satisfied.
RCP<NOX::StatusTest::Combo> combo =
rcp (new NOX::StatusTest::Combo (NOX::StatusTest::Combo::OR,
testNormF, testMaxIters));
// Create the NOX nonlinear solver.
RCP<NOX::Solver::Generic> solver =
NOX::Solver::buildSolver (group, combo, params);
// Solve the nonlinear system.
NOX::StatusTest::StatusType status = solver->solve();
// Print the result.
if (Comm.MyPID() == 0) {
cout << endl << "-- Parameter List From Solver --" << endl;
solver->getList ().print (cout);
}
// Get the Epetra_Vector with the final solution from the solver.
const NOX::Epetra::Group& finalGroup =
dynamic_cast<const NOX::Epetra::Group&> (solver->getSolutionGroup ());
const Epetra_Vector& finalSolution =
dynamic_cast<const NOX::Epetra::Vector&> (finalGroup.getX
()).getEpetraVector ();
// Add a barrier and flush cout, just to make it less likely that
// the output will get mixed up when running with multiple MPI
// processes.
Comm.Barrier ();
cout.flush ();
if (Comm.MyPID() == 0) {
cout << "Computed solution : " << endl;
}
// Add a barrier and flush cout, so that the above header line
// appears before the rest of the vector data.
Comm.Barrier ();
cout.flush ();
// Epetra objects know how to print themselves politely when
// their operator<<(std::ostream&) is invoked on all MPI
// process(es) in the communicator to which they are associated.
cout << finalSolution;
#ifdef HAVE_MPI
MPI_Finalize();
#endif
return EXIT_SUCCESS;
}
Wiki Pages
Trilinos Hands On Tutorial
[Zoltan Hands On Tutorial] (ZoltanHandsOnTutorial)
Links
Trilinos Home Page
Trilinos "Getting Started"