PyTrilinos Logo PyTrilinos.NOX

PyTrilinos.NOX

Python versions of C++ methods that expect Teuchos::ParameterList arguments accept either Teuchos.ParameterList objects or python dictionaries.

Since python objects are already reference counted, the Teuchos::RCP used in the C++ version of NOX is hidden from python users. Python versions of C++ methods that accept Teuchos::RCP<object> arguments accept raw object arguments in python.

The following NOX namespaces are suported in python:

NOX
NOX.Abstract
NOX.Epetra
NOX.Epetra.Interface
NOX.Solver
NOX.StatusTest

The only NOX Interface class that has a python wrapper is the Epetra interface. As with the C++ version of NOX, you define a nonlinear problem by declaring a python class that inherits from NOX.Epetra.Interface.Required. Optionally, your class may also inherit from NOX.Epetra.Interface.Jacobian and/or NOX.Epetra.Interface.Preconditioner.

Your constructor must call the constructors of its NOX.Epetra.Interface base classes:

class MyProblem(NOX.Epetra.Interface.Required):
  def __init__(self):
    NOX.Epetra.Interface.Required.__init__(self)

This is necessary because the underlying callback mechanism needs to be properly initialized. The nonlinear function for your class is implemented by defining a computeF() method:

def computeF(self, x, F, flag):
  "Required implementation of computeF() method"
  ...

Arguments x and F are passed from the underlying C++ code as Epetra_Vector objects and then converted to numpy-hybrid Epetra.Vector objects when they are passed into your python computeF() function. Therefore, you can use the numpy.ndarray interface on these arguments, such as the shape attribute, or slice indexing.

The flag argument is an integer that NOX uses to inform computeF() why it is being called. You can use this flag to alter how computeF() behaves. See the C++ NOX documentation for details.

We are defining a somewhat complicated python-to-C++-to-python callback scheme here. Unfortunately, error-handling in such an environment is not as rubust as in a single-language environment. Specifically, if your computeF() (accidentally) raises a python exception, all you may see is:

terminate called after throwing an instance of 'Swig::DirectorMethodException'
Abort trap

For this reason, it is a good idea to wrap your computations in a try block:

class MyProblem(NOX.Epetra.Interface.Required):

  def __init__(self):
    NOX.Epetra.Interface.Required.__init__(self)

  def computeF(self, x, F, flag):
    "Required implementation of computeF() method"
    try:
      F[:] = nonlinear_func(x)  # Whatever this may be...
    except Exception, e:
      print "Python exception raised in MyProblem.computeF:"
      print e
      return False
    return True

This will print the python exception if one is raised, and tells NOX that the computation failed. Remember to return a boolean indicating success or failure of the computeF() method.

NOX needs to be able to compute the result of the Jacobian of F(x) on a given vector from the same space as x. The NOX.Epetra.Interface.Required class and computeF() method are sufficient to estimate this product if you are willing to use the NOX.Epetra.FiniteDifference class. If you want to use an algebraic preconditioner based on an approximation to the Jacobian, you will want to use map coloring to greatly improve the efficiency, specifically the NOX.Epetra.FiniteDifferenceColoring class. See exNOX_1Dfem.py in the example directory of the PyTrilinos package for an example of this type of implementation.

You solve your problem by first creating an instance of your MyProblem class and an Epetra.Vector solution (using some appropriate constructor):

problem = MyProblem()
soln    = Epetra.Vector(...)

Since we do not inherit from Jacobian or Preconditioner, we need to define a matrix-free linear system for NOX to use:

mf      = NOX.Epetra.MatrixFree({ }, problem, soln)
fdc     = NOX.Epetra.FiniteDifferenceColoring({ }, { }, soln,
                                              problem.getGraph())
linSys  = NOX.Epetra.LinearSystemAztecOO({ }, { }, mf, mf, fdc, fdc,
                                         soln)

Note that these calls assume that your MyProblem class defines a getGraph() method that returns an Epetra.CrsGraph that defines the sparsity/coupling of the nonlinear problem. Also, the empty python dictionaries result in default parameter specifications. Now we can create a NOX.Epetra.Group and a NOX.Solver.Manager:

group   = NOX.Epetra.Group({ }, problem, soln, linSys)
solver  = NOX.Solver.Manager(group, statusTest, { })

Here we assume statusTest is a properly constructed NOX.StatusTest object. Now that everything is specified, we can solve the problem with:

status  = solver.solve()

Simple, no?