"""
This example demonstrates the use of TAO for Python to solve an
unconstrained minimization problem on a single processor.

We minimize the extended Rosenbrock function::

   sum_{i=0}^{n/2-1} ( alpha*(x_{2i+1}-x_{2i}^2)^2 + (1-x_{2i})^2 )
"""

try: range = xrange
except NameError: pass

# the two lines below are only
# needed to build options database
# from command line arguments
import sys, petsc4py
petsc4py.init(sys.argv)

from petsc4py import PETSc


class AppCtx(object):

    """
    Extended Rosenbrock function.
    """

    def __init__(self, n=2, alpha=99.0):
        self.size  = int(n)
        self.alpha = float(alpha)

    def formObjective(self, tao, x):
        #print 'AppCtx.formObjective()'
        alpha = self.alpha
        nn = self.size // 2
        ff = 0.0
        for i in range(nn):
            t1 = x[2*i+1] - x[2*i] * x[2*i]
            t2 = 1 - x[2*i];
            ff += alpha*t1*t1 + t2*t2;
        return ff

    def formGradient(self, tao, x, G):
        #print 'AppCtx.formGradient()'
        alpha = self.alpha
        nn = self.size // 2
        G.zeroEntries()
        for i in range(nn):
            t1 = x[2*i+1] - x[2*i] * x[2*i]
            t2 = 1 - x[2*i];
            G[2*i]   = -4*alpha*t1*x[2*i] - 2*t2;
            G[2*i+1] = 2*alpha*t1;

    def formObjGrad(self, tao, x, G):
        #print 'AppCtx.formObjGrad()'
        alpha = self.alpha
        nn = self.size // 2
        ff = 0.0
        G.zeroEntries()
        for i in range(nn):
            t1 = x[2*i+1] - x[2*i] * x[2*i]
            t2 = 1 - x[2*i];
            ff += alpha*t1*t1 + t2*t2;
            G[2*i]   = -4*alpha*t1*x[2*i] - 2*t2;
            G[2*i+1] = 2*alpha*t1;
        return ff

    def formHessian(self, tao, x, H, HP):
        #print 'AppCtx.formHessian()'
        alpha = self.alpha
        nn = self.size // 2
        idx = [0, 0]
        v = [[0.0, 0.0],
             [0.0, 0.0]]
        H.zeroEntries()
        for i in range(nn):
            v[1][1] = 2*alpha
            v[0][0] = -4*alpha*(x[2*i+1]-3*x[2*i]*x[2*i]) + 2
            v[1][0] = v[0][1] = -4.0*alpha*x[2*i];
            idx[0] = 2*i
            idx[1] = 2*i+1
            H[idx,idx] = v
        H.assemble()

# access PETSc options database
OptDB = PETSc.Options()


# create user application context
# and configure user parameters
user = AppCtx()
user.size  = OptDB.getInt (    'n', user.size)
user.alpha = OptDB.getReal('alpha', user.alpha)

# create solution vector
x = PETSc.Vec().create(PETSc.COMM_SELF)
x.setSizes(user.size)
x.setFromOptions()

# create Hessian matrix
H = PETSc.Mat().create(PETSc.COMM_SELF)
H.setSizes([user.size, user.size])
H.setFromOptions()
H.setOption(PETSc.Mat.Option.SYMMETRIC, True)
H.setUp()

# pass the following to command line:
#  $ ... -methods nm,lmvm,nls,ntr,cg,blmvm,tron
# to try many methods
methods = OptDB.getString('methods', '')
methods = methods.split(',')
for meth in methods:
    # create TAO Solver
    tao = PETSc.TAO().create(PETSc.COMM_SELF)
    if meth: tao.setType(meth)
    tao.setFromOptions()
    # solve the problem
    tao.setObjectiveGradient(user.formObjGrad)
    tao.setObjective(user.formObjective)
    tao.setGradient(user.formGradient)
    tao.setHessian(user.formHessian, H)
    #app.getKSP().getPC().setFromOptions()
    x.set(0) # zero initial guess
    tao.setSolution(x)
    tao.solve()
    tao.destroy()

## # this is just for testing
## x     = app.getSolution()
## G     = app.getGradient()
## H, HP = app.getHessian()

## f = tao.computeObjective(x)
## tao.computeGradient(x, G)
## f = tao.computeObjectiveGradient(x, G)
## tao.computeHessian(x, H, HP)
