#****************************************************#
# This file is part of OPTALG. #
# #
# Copyright (c) 2015-2017, Tomas Tinoco De Rubira. #
# #
# OPTALG is released under the BSD 2-clause license. #
#****************************************************#
import numpy as np
from types import MethodType
from scipy.sparse import eye, bmat, coo_matrix
[docs]class OptProblem(object):
"""
Class for representing general optimization problems.
"""
def __init__(self):
"""
Class for representing general optimization problems.
Parameters
----------
problem : Object
"""
#: Objective function value
self.phi = 0
#: Objective function gradient
self.gphi = None
#: Objective function Hessian (lower triangular)
self.Hphi = None
#: Matrix for linear equality constraints
self.A = None
#: Right-hand side for linear equality constraints
self.b = None
#: Nonlinear equality constraint function
self.f = None
#: Jacobian of nonlinear constraints
self.J = None
#: Linear combination of Hessians of nonlinear constraints
self.H_combined = None
#: Upper limits
self.u = None
#: Lower limits
self.l = None
#: Integer flags (boolean array)
self.P = None
#: Initial point
self.x = None
#: Lagrande multipliers for linear equality constraints
self.lam = None
#: Lagrande multipliers for nonlinear equality constraints
self.nu = None
#: Lagrande multipliers for upper limits
self.mu = None
#: Lagrande multipliers for lower limits
self.pi = None
#: Wrapped problem
self.wrapped_problem = None
[docs] def recover_primal_variables(self,x):
"""
Recovers primal variables for original problem.
Parameters
----------
x : ndarray
"""
return x
[docs] def recover_dual_variables(self,lam,nu,mu,pi):
"""
Recovers dual variables for original problem.
Parameters
----------
lam : ndarray
nu : ndarray
mu : ndarray
pi : ndarray
"""
return lam,nu,mu,pi
[docs] def get_num_primal_variables(self):
"""
Gets number of primal variables.
Returns
-------
num : int
"""
if self.x is not None:
return self.x.size
if self.gphi is not None:
return self.gphi.size
if self.Hphi is not None:
return self.Hphi.shape[0]
if self.A is not None:
return self.A.shape[1]
if self.J is not None:
return self.J.shape[1]
if self.u is not None:
return self.u.size
if self.l is not None:
return self.l.size
return 0
[docs] def get_num_linear_equality_constraints(self):
"""
Gets number of linear equality constraints.
Returns
-------
num : int
"""
if self.A is not None:
return self.A.shape[0]
return 0
[docs] def get_num_nonlinear_equality_constraints(self):
"""
Gets number of nonlinear equality constraints.
Returns
-------
num : int
"""
if self.f is not None:
return self.f.size
return 0
[docs] def combine_H(self,coeff,ensure_psd=False):
"""
Forms and saves a linear combination of the individual constraint Hessians.
Parameters
----------
coeff : vector
ensure_psd : {``True``,``False``}
"""
pass
[docs] def eval(self,x):
"""
Evaluates the objective value and constraints
at the give point.
Parameters
----------
x : vector
"""
pass
[docs] def show(self):
"""
Displays information about the problem.
"""
pass
def cast_problem(problem):
"""
Casts problem object with known interface as OptProblem.
Parameters
----------
problem : Object
"""
# Optproblem
if isinstance(problem,OptProblem):
return problem
# Other
else:
# Type Base
if (not hasattr(problem,'G') or
(problem.G.shape[0] == problem.G.shape[1] and
problem.G.shape[0] == problem.G.nnz and
np.all(problem.G.row == problem.G.col) and
np.all(problem.G.data == 1.))):
return create_problem_from_type_base(problem)
# Type A
else:
return create_problem_from_type_A(problem)
def create_problem_from_type_base(problem):
"""
Creates OptProblem from type-base problem.
Parameters
----------
problem : Object
"""
p = OptProblem()
# Init attributes
p.phi = problem.phi
p.gphi = problem.gphi
p.Hphi = problem.Hphi
p.A = problem.A
p.b = problem.b
p.f = problem.f
p.J = problem.J
p.H_combined = problem.H_combined
p.u = problem.u
p.l = problem.l
p.x = problem.x
p.P = None
p.lam = None
p.nu = None
p.mu = None
p.pi = None
p.wrapped_problem = problem
# Methods
def eval(cls,x):
cls.wrapped_problem.eval(x)
cls.phi = cls.wrapped_problem.phi
cls.gphi = cls.wrapped_problem.gphi
cls.Hphi = cls.wrapped_problem.Hphi
cls.f = cls.wrapped_problem.f
cls.J = cls.wrapped_problem.J
def combine_H(cls,coeff,ensure_psd=False):
cls.wrapped_problem.combine_H(coeff,ensure_psd)
cls.H_combined = cls.wrapped_problem.H_combined
p.eval = MethodType(eval,p)
p.combine_H = MethodType(combine_H,p)
# Return
return p
def create_problem_from_type_A(problem):
"""
Creates OptProblem from type-A problem.
Parameters
----------
problem : Object
"""
p = OptProblem()
nx = problem.get_num_primal_variables()
nz = problem.G.shape[0]
p.phi = problem.phi
p.gphi = np.hstack((problem.gphi,np.zeros(nz)))
p.Hphi = coo_matrix((problem.Hphi.data,(problem.Hphi.row,problem.Hphi.col)),shape=(nx+nz,nx+nz))
p.A = bmat([[problem.A,None],[problem.G,-eye(nz)]],format='coo')
p.b = np.hstack((problem.b,np.zeros(nz)))
p.f = problem.f
p.J = coo_matrix((problem.J.data,(problem.J.row,problem.J.col)),shape=(problem.J.shape[0],nx+nz))
p.H_combined = coo_matrix((problem.H_combined.data,(problem.H_combined.row,problem.H_combined.col)),shape=(nx+nz,nx+nz))
p.u = np.hstack((problem.get_upper_limits(),problem.u))
p.l = np.hstack((problem.get_lower_limits(),problem.l))
p.x = np.hstack((problem.x,np.zeros(nz)))
p.P = None
p.lam = None
p.nu = None
p.mu = None
p.pi = None
p.wrapped_problem = problem
def eval(cls,xz):
x = xz[:nx]
z = xz[nx:]
prob = cls.wrapped_problem
prob.eval(x)
cls.phi = prob.phi
cls.gphi = np.hstack((prob.gphi,np.zeros(nz)))
cls.Hphi = coo_matrix((prob.Hphi.data,(prob.Hphi.row,prob.Hphi.col)),shape=(nx+nz,nx+nz))
cls.f = prob.f
cls.J = coo_matrix((prob.J.data,(prob.J.row,prob.J.col)),shape=(prob.J.shape[0],nx+nz))
def combine_H(cls,coeff,ensure_psd=False):
prob = cls.wrapped_problem
prob.combine_H(coeff,ensure_psd=ensure_psd)
cls.H_combined = coo_matrix((prob.H_combined.data,(prob.H_combined.row,prob.H_combined.col)),shape=(nx+nz,nx+nz))
def recover_primal_variables(cls,x):
return x[:nx]
def recover_dual_variables(cls,lam,nu,mu,pi):
prob = cls.wrapped_problem
return lam[:prob.A.shape[0]],nu,mu[nx:],pi[nx:]
p.eval = MethodType(eval,p)
p.combine_H = MethodType(combine_H,p)
p.recover_primal_variables = MethodType(recover_primal_variables,p)
p.recover_dual_variables = MethodType(recover_dual_variables,p)
# Return
return p