def __init__(self, nlp, **kwargs): self.nlp = nlp # Temporary error message as the class does not yet support # range constraints if nlp.nrangeC > 0: msg = 'Range inequality constraints are not supported.' raise ValueError, msg # Analyze NLP to add slack variables to the formulation # Ordering of the slacks in 'x' is assumed to be the order shown here self.nx = nlp.n self.nsLL = nlp.nlowerC self.nsUU = nlp.nupperC # self.nsLR = nlp.nrangeC # self.nsUR = nlp.nrangeC self.ns = nlp.nlowerC + nlp.nupperC + 2*nlp.nrangeC self.n = self.nx + self.ns self.m = nlp.m # Copy initial data from NLP given new problem definition # Initialize slack variables to zero self.x0 = numpy.zeros(self.n,'d') self.x0[:self.nx] = nlp.x0 self.pi0 = nlp.pi0.copy() # Create Hessian approximation by default self.approxHess = kwargs.get('approxHess',True) if self.approxHess: # LBFGS is currently the only option self.Hessapp = LBFGS(self.n) # Extend bound arrays to include slack variables self.Lvar = numpy.zeros(self.n,'d') self.Lvar[:self.nx] = nlp.Lvar self.Uvar = nlp.Infinity*numpy.ones(self.n,'d') self.Uvar[:self.nx] = nlp.Uvar # Bring in bound arrays for constraints and lists of constraint types self.Lcon = nlp.Lcon self.Ucon = nlp.Ucon self.lowerC = nlp.lowerC self.upperC = nlp.upperC # self.rangeC = nlp.rangeC self.equalC = nlp.equalC
def store(self, new_s, new_y): # Simply swap s and y. LBFGS.store(self, new_y, new_s)
class AugmentedLagrangian(NLPModel): ''' This class is a reformulation of an NLP, used to compute the augmented Lagrangian function, gradient, and approximate Hessian in a method-of-multipliers optimization routine. Slack variables are introduced for inequality constraints and a function that computes the gradient projected on to variable bounds is included. Matrix-free NLP models are accomodated with the help of a Hessian approximation which can be updated and restarted via calls to methods in this class. ''' def __init__(self, nlp, **kwargs): self.nlp = nlp # Temporary error message as the class does not yet support # range constraints if nlp.nrangeC > 0: msg = 'Range inequality constraints are not supported.' raise ValueError, msg # Analyze NLP to add slack variables to the formulation # Ordering of the slacks in 'x' is assumed to be the order shown here self.nx = nlp.n self.nsLL = nlp.nlowerC self.nsUU = nlp.nupperC # self.nsLR = nlp.nrangeC # self.nsUR = nlp.nrangeC self.ns = nlp.nlowerC + nlp.nupperC + 2*nlp.nrangeC self.n = self.nx + self.ns self.m = nlp.m # Copy initial data from NLP given new problem definition # Initialize slack variables to zero self.x0 = numpy.zeros(self.n,'d') self.x0[:self.nx] = nlp.x0 self.pi0 = nlp.pi0.copy() # Create Hessian approximation by default self.approxHess = kwargs.get('approxHess',True) if self.approxHess: # LBFGS is currently the only option self.Hessapp = LBFGS(self.n) # Extend bound arrays to include slack variables self.Lvar = numpy.zeros(self.n,'d') self.Lvar[:self.nx] = nlp.Lvar self.Uvar = nlp.Infinity*numpy.ones(self.n,'d') self.Uvar[:self.nx] = nlp.Uvar # Bring in bound arrays for constraints and lists of constraint types self.Lcon = nlp.Lcon self.Ucon = nlp.Ucon self.lowerC = nlp.lowerC self.upperC = nlp.upperC # self.rangeC = nlp.rangeC self.equalC = nlp.equalC # end def # Evaluate infeasibility measure (used in both objective and gradient) def get_infeas(self, x, **kwargs): nx = self.nx nsLL_ind = nx + self.nsLL nsUU_ind = nsLL_ind + self.nsUU # nsLR_ind = nsUU_ind + self.nsLR # nsUR_ind = nsLR_ind + self.nsUR convals = self.nlp.cons(x[:nx]) convals[self.lowerC] -= x[nx:nsLL_ind] + self.Lcon[self.lowerC] convals[self.upperC] += x[nsLL_ind:nsUU_ind] - self.Ucon[self.upperC] convals[self.equalC] -= self.Lcon[self.equalC] # convals[self.rangeC] += x[nsLR_ind:nsUR_ind] - x[nsUU_ind:nsLR_ind] return convals # end def # Evaluate augmented Lagrangian function def obj(self, x, pi, rho, **kwargs): nx = self.nx nsLL_ind = nx + self.nsLL nsUU_ind = nsLL_ind + self.nsUU # nsLR_ind = nsUU_ind + self.nsLR # nsUR_ind = nsLR_ind + self.nsUR alfunc = self.nlp.obj(x[:nx]) convals = self.get_infeas(x) alfunc += numpy.dot(pi,convals) alfunc += 0.5*rho*numpy.sum(convals**2) return alfunc # end def # Evaluate augmented Lagrangian gradient def grad(self, x, pi, rho, **kwargs): nlp = self.nlp nx = self.nx nsLL_ind = nx + self.nsLL nsUU_ind = nsLL_ind + self.nsUU # nsLR_ind = nsUU_ind + self.nsLR # nsUR_ind = nsLR_ind + self.nsUR algrad = numpy.zeros(self.n,'d') algrad[:nx] = nlp.grad(x[:nx]) convals = self.get_infeas(x) vec = pi + rho*convals if isinstance(nlp, MFModel): algrad[:nx] += nlp.jtprod(x[:nx],vec) else: algrad[:nx] += rho*numpy.dot(nlp.jac(x[:nx]).transpose(),vec) # end if algrad[nx:nsLL_ind] = -pi[nlp.lowerC] - rho*convals[nlp.lowerC] algrad[nsLL_ind:nsUU_ind] = pi[nlp.upperC] + rho*convals[nlp.upperC] # **Range constraint slacks here** return algrad # end def def project_gradient(self, x, g, **kwargs): ''' Project the provided gradient on to the bound-constrained space and return the result. This is a helper function for determining optimality conditions of the original NLP. ''' p = x - g med = numpy.maximum(numpy.minimum(p,self.Uvar),self.Lvar) q = x - med return q def hprod(self, x, pi, rho, v, **kwargs): ''' Compute the Hessian-vector produce of the Hessian of the augmented Lagrangian with arbitrary vector v. Both exact and approximate Hessians are supported. ''' nlp = self.nlp nx = self.nx w = numpy.zeros(self.n,'d') # Non-slack variables if self.approxHess: # Approximate Hessian w = self.Hessapp.matvec(v) else: # Exact Hessian # Note: the code in this block has yet to be properly tested convals = self.get_infeas(x) w[:nx] = nlp.hprod(x[:nx],pi,v[:nx],**kwargs) for i in range(self.m): w[:nx] += rho*convals[i]*nlp.hiprod(i,x[:nx],v[:nx]) # end for if isinstance(nlp, MFModel): w[:nx] += rho*nlp.jtprod(x[:nx],nlp.jprod(x[:nx],v[:nx])) w[:nx] += rho*nlp.jprod(x[:nx],v[:nx]) w[nx:] += rho*nlp.jtprod(x[:nx],v[nx:]) else: J = nlp.jac w[:nx] += rho*numpy.dot(J.transpose(),numpy.dot(J,v[:nx])) w[:nx] += rho*numpy.dot(J,v[:nx]) w[nx:] += rho*numpy.dot(J.transpose(),v[nx:]) # end if # Slack variables w[nx:] += rho*v[nx:] # end if return w def hupdate(self, new_s=None, new_y=None): if self.approxHess and new_s is not None and new_y is not None: self.Hessapp.store(new_s,new_y) return def hrestart(self): if self.approxHess: self.Hessapp.restart() return
def __init__(self, n, npairs=5, **kwargs): LBFGS.__init__(self, n, npairs, **kwargs)