# Path: nlp/model/nlpmodel.py
# class UnconstrainedNLPModel(NLPModel):
#     """Generic class to represent an unconstrained problem."""
# 
#     def __init__(self, nvar, **kwargs):
#         """Initialize an unconstrained problem with ``nvar`` variables.
# 
#         See the documentation of :class:`NLPModel` for more information.
#         """
#         # Discard options related to constrained problems.
#         kwargs.pop('m', None)
#         kwargs.pop('ncon', None)
#         kwargs.pop('Lcon', None)
#         kwargs.pop('Ucon', None)
#         kwargs.pop('Lvar', None)
#         kwargs.pop('Uvar', None)
#         super(UnconstrainedNLPModel, self).__init__(nvar, **kwargs)
# 
#     def cons(self, x):
#         """Evaluate the constraints at x."""
#         return np.zeros(self.m, dtype=np.float)
# 
#     def jprod(self, x, v):
#         """Evaluate Jacobian-vector product at x with p."""
#         return np.zeros(self.m, dtype=np.float)
# 
#     def jtprod(self, x, v):
#         """Evaluate transposed-Jacobian-vector product at x with p."""
#         return np.zeros(self.n, dtype=np.float)
# 
# Path: nlp/model/qnmodel.py
# class QuasiNewtonModel(NLPModel):
#     """`NLPModel with a quasi-Newton Hessian approximation."""
# 
#     def __init__(self, *args, **kwargs):
#         """Instantiate a model with quasi-Newton Hessian approximation.
# 
#         :keywords:
#             :H: the `class` of a quasi-Newton linear operator.
#                 This keyword is mandatory.
# 
#         Keywords accepted by the quasi-Newton class will be passed
#         directly to its constructor.
#         """
#         super(QuasiNewtonModel, self).__init__(*args, **kwargs)
#         qn_cls = kwargs.pop('H')
#         self._H = qn_cls(self.nvar, **kwargs)
# 
#     @property
#     def H(self):
#         """Quasi-Newton Hessian approximation."""
#         return self._H
# 
#     def hess(self, *args, **kwargs):
#         """Evaluate Lagrangian Hessian at (x, z).
# 
#         This is an alias for `self.H`.
#         """
#         return self.H
# 
#     def hop(self, *args, **kwargs):
#         """Obtain Lagrangian Hessian at (x, z) as a linear operator.
# 
#         This is an alias for `self.H`.
#         """
#         return self.H
# 
#     def hprod(self, x, z, v, **kwargs):
#         """Hessian-vector product."""
#         return self.H * v


import numpy as np
from nlp.model.nlpmodel import UnconstrainedNLPModel
from nlp.model.qnmodel import QuasiNewtonModel
    def grad(self, x):
        n = self.nvar
        g = np.empty(n)
        g[0] = -400 * x[0] * (x[1] - x[0]**2) - 2 * (1 - x[0])
        g[-1] = 200 * (x[-1] - x[-2]**2)
        g[1:-1] = 200 * (x[1:-1] - x[:-2]**2) - \
                  400 * x[1:-1] * (x[2:] - x[1:-1]**2) - 2 * (1 - x[1:-1])
        return g

    def diags(self, x):
        n = self.nvar
        d = np.empty(n)
        d[:-1] = 800 * x[:-1]**2 - 400 * (x[1:] - x[:-1]**2) + 2
        d[1:] += 200
        d[-1] = 200
        o = -400 * x[:-1]
        return (d, o)

    def hess(self, x, z):
        (d, o) = self.diags(x)
        return np.diag(d) + np.diag(o, 1) + np.diag(o, -1)

    def hprod(self, x, z, v):
        (d, o) = self.diags(x)
        hv = d * v
        hv[1:] += o * x[1:]
        hv[:-1] += o * x[:-1]
        return hv



# Path: nlp/model/nlpmodel.py
# class UnconstrainedNLPModel(NLPModel):
#     """Generic class to represent an unconstrained problem."""
# 
#     def __init__(self, nvar, **kwargs):
#         """Initialize an unconstrained problem with ``nvar`` variables.
# 
#         See the documentation of :class:`NLPModel` for more information.
#         """
#         # Discard options related to constrained problems.
#         kwargs.pop('m', None)
#         kwargs.pop('ncon', None)
#         kwargs.pop('Lcon', None)
#         kwargs.pop('Ucon', None)
#         kwargs.pop('Lvar', None)
#         kwargs.pop('Uvar', None)
#         super(UnconstrainedNLPModel, self).__init__(nvar, **kwargs)
# 
#     def cons(self, x):
#         """Evaluate the constraints at x."""
#         return np.zeros(self.m, dtype=np.float)
# 
#     def jprod(self, x, v):
#         """Evaluate Jacobian-vector product at x with p."""
#         return np.zeros(self.m, dtype=np.float)
# 
#     def jtprod(self, x, v):
#         """Evaluate transposed-Jacobian-vector product at x with p."""
#         return np.zeros(self.n, dtype=np.float)
# 
# Path: nlp/model/qnmodel.py
# class QuasiNewtonModel(NLPModel):
#     """`NLPModel with a quasi-Newton Hessian approximation."""
# 
#     def __init__(self, *args, **kwargs):
#         """Instantiate a model with quasi-Newton Hessian approximation.
# 
#         :keywords:
#             :H: the `class` of a quasi-Newton linear operator.
#                 This keyword is mandatory.
# 
#         Keywords accepted by the quasi-Newton class will be passed
#         directly to its constructor.
#         """
#         super(QuasiNewtonModel, self).__init__(*args, **kwargs)
#         qn_cls = kwargs.pop('H')
#         self._H = qn_cls(self.nvar, **kwargs)
# 
#     @property
#     def H(self):
#         """Quasi-Newton Hessian approximation."""
#         return self._H
# 
#     def hess(self, *args, **kwargs):
#         """Evaluate Lagrangian Hessian at (x, z).
# 
#         This is an alias for `self.H`.
#         """
#         return self.H
# 
#     def hop(self, *args, **kwargs):
#         """Obtain Lagrangian Hessian at (x, z) as a linear operator.
# 
#         This is an alias for `self.H`.
#         """
#         return self.H
# 
#     def hprod(self, x, z, v, **kwargs):
#         """Hessian-vector product."""
#         return self.H * v


# Path: examples/rosenbrock_dense.py
import numpy as np
from nlp.model.nlpmodel import UnconstrainedNLPModel
from nlp.model.qnmodel import QuasiNewtonModel

    def grad(self, x):
        n = self.nvar
        g = np.empty(n)
        g[0] = -400 * x[0] * (x[1] - x[0]**2) - 2 * (1 - x[0])
        g[-1] = 200 * (x[-1] - x[-2]**2)
        g[1:-1] = 200 * (x[1:-1] - x[:-2]**2) - \
                  400 * x[1:-1] * (x[2:] - x[1:-1]**2) - 2 * (1 - x[1:-1])
        return g

    def diags(self, x):
        n = self.nvar
        d = np.empty(n)
        d[:-1] = 800 * x[:-1]**2 - 400 * (x[1:] - x[:-1]**2) + 2
        d[1:] += 200
        d[-1] = 200
        o = -400 * x[:-1]
        return (d, o)

    def hess(self, x, z):
        (d, o) = self.diags(x)
        return np.diag(d) + np.diag(o, 1) + np.diag(o, -1)

    def hprod(self, x, z, v):
        (d, o) = self.diags(x)
        hv = d * v
        hv[1:] += o * x[1:]
        hv[:-1] += o * x[:-1]
        return hv



class QNRosenbrock(QuasiNewtonModel, Rosenbrock):