def _solve_isolating(self,*args): """ Description ----------- Isolate parameters and then solve program. Used by linearize method to obtain subgradient. Arguments must be numbers. A very important point is that solve_isolating introduces equality constraints and places them at the beginning of the constraint list. This is later used to construct the subgradient. """ # Process input if(len(args) != 0 and type(args[0]) is list): args = args[0] # Create argument list arg_list = [] for arg in args: if(np.isscalar(arg)): arg_list += [arg] elif(type(arg) is cvxpy_matrix): (m,n) = arg.shape for i in range(0,m,1): for j in range(0,n,1): arg_list += [arg[i,j]] else: raise ValueError('Arguments must be numeric') # Check number of arguments if(len(arg_list) != len(self.params)): raise ValueError('Invalid number of arguments') # Isolate parameters p1_map = {} new_constr = cvxpy_list([]) for p in self.params: v = var('v_'+p.name) p1_map[p] = v new_constr += cvxpy_list([equal(v,p)]) new_p1 = prog((self.action,re_eval(self.obj,p1_map)), new_constr+re_eval(self.constr,p1_map), self.params, self.options,self.name) # Substitute parameters with arguments p2_map = {} for k in range(0,len(arg_list),1): p2_map[new_p1.params[k]] = arg_list[k] new_p2 = prog((new_p1.action,re_eval(new_p1.obj,p2_map)), re_eval(new_p1.constr,p2_map), [],new_p1.options,new_p1.name) # Solve program obj,lagrange_mul_eq = solve_prog(new_p2) self.lagrange_mul_eq = lagrange_mul_eq return obj
def _solve_isolating(self, *args): """ Description ----------- Isolate parameters and then solve program. Used by linearize method to obtain subgradient. Arguments must be numbers. A very important point is that solve_isolating introduces equality constraints and places them at the beginning of the constraint list. This is later used to construct the subgradient. """ # Process input if (len(args) != 0 and type(args[0]) is list): args = args[0] # Create argument list arg_list = [] for arg in args: if (np.isscalar(arg)): arg_list += [arg] elif (type(arg) is cvxpy_matrix): (m, n) = arg.shape for i in range(0, m, 1): for j in range(0, n, 1): arg_list += [arg[i, j]] else: raise ValueError('Arguments must be numeric') # Check number of arguments if (len(arg_list) != len(self.params)): raise ValueError('Invalid number of arguments') # Isolate parameters p1_map = {} new_constr = cvxpy_list([]) for p in self.params: v = var('v_' + p.name) p1_map[p] = v new_constr += cvxpy_list([equal(v, p)]) new_p1 = prog((self.action, re_eval(self.obj, p1_map)), new_constr + re_eval(self.constr, p1_map), self.params, self.options, self.name) # Substitute parameters with arguments p2_map = {} for k in range(0, len(arg_list), 1): p2_map[new_p1.params[k]] = arg_list[k] new_p2 = prog((new_p1.action, re_eval(new_p1.obj, p2_map)), re_eval(new_p1.constr, p2_map), [], new_p1.options, new_p1.name) # Solve program obj, lagrange_mul_eq = solve_prog(new_p2) self.lagrange_mul_eq = lagrange_mul_eq return obj
def _get_equivalent(self): """ Description ----------- Construct a new program by applying the transform algorithm to the constraints. None: It is assumed that the program has been previously expanded. """ return prog((self.action, self.obj), transform(self.constr), [], self.options)
def _get_equivalent(self): """ Description ----------- Construct a new program by applying the transform algorithm to the constraints. None: It is assumed that the program has been previously expanded. """ return prog((self.action,self.obj), transform(self.constr),[],self.options)
def _get_expanded(self): """ Description ----------- Construct a new program by applying the expansion algorithm to the objective and constraints. """ new_obj,new_eq = expand(self.obj) more_constr = expand(self.constr) return prog((self.action,new_obj), more_constr+new_eq,[],self.options)
def _get_cvx_relaxation(self): """ Description ----------- Construct a new program by choosing the convex constraints and ignoring the other ones. Note: It is assumed that the program has been expanded and transformed previously. """ return prog((self.action, self.obj), self.constr._get_convex(), [], self.options)
def _get_cvx_relaxation(self): """ Description ----------- Construct a new program by choosing the convex constraints and ignoring the other ones. Note: It is assumed that the program has been expanded and transformed previously. """ return prog((self.action,self.obj), self.constr._get_convex(),[],self.options)
def is_dcp(self,args=None): """ Description ----------- Checks if the program follows DCP rules. If args is None, the check is done on the body of the program. If args is a list of arguments, the parameters are replaced with the arguments and the check is done on the resulting program. This function is called with a list of arguments when cvxpy_tree.is_dcp is executed. Arguments --------- args: List of arguments """ # No arguments: Check body of program if(args == None): if(self.action == MINIMIZE and not self.obj.is_convex()): return False elif(self.action == MAXIMIZE and not self.obj.is_concave()): return False else: return self.constr.is_dcp() # Arguments given: Replace parameters and then check else: # Check if some argument has parameters if(len(cvxpy_list(args).get_params()) != 0): return False # Create a param-arg map p_map = {} for k in range(0,len(args),1): p_map[self.params[k]] = args[k] # Re-evaluate new_p = prog((self.action,re_eval(self.obj,p_map)), re_eval(self.constr,p_map)) # Check dcp on resulting program return new_p.is_dcp()
def is_dcp(self, args=None): """ Description ----------- Checks if the program follows DCP rules. If args is None, the check is done on the body of the program. If args is a list of arguments, the parameters are replaced with the arguments and the check is done on the resulting program. This function is called with a list of arguments when cvxpy_tree.is_dcp is executed. Arguments --------- args: List of arguments """ # No arguments: Check body of program if (args == None): if (self.action == MINIMIZE and not self.obj.is_convex()): return False elif (self.action == MAXIMIZE and not self.obj.is_concave()): return False else: return self.constr.is_dcp() # Arguments given: Replace parameters and then check else: # Check if some argument has parameters if (len(cvxpy_list(args).get_params()) != 0): return False # Create a param-arg map p_map = {} for k in range(0, len(args), 1): p_map[self.params[k]] = args[k] # Re-evaluate new_p = prog((self.action, re_eval(self.obj, p_map)), re_eval(self.constr, p_map)) # Check dcp on resulting program return new_p.is_dcp()
def _pm_expand(self,constr): """ Description ----------- Given the constraint, which must be in the form self(args) operator variable, the parameters are replaced with arguments and then the partial minimization description of the program is merged with the constraint. Argument -------- constr: cvxpy_constr of the form self(args) operator variable. """ # Get arguments args = constr.left.children # Create arg-param map by position p_map = {} for k in range(0,len(args),1): p_map[self.params[k]] = args[k] # Create new program new_p = prog((self.action,re_eval(self.obj,p_map)), re_eval(self.constr,p_map),[], self.options,self.name) # Expand partial minimization right = constr.right new_constr = [] if(self.curvature == CONVEX): new_constr += [less(new_p.obj,right)] else: new_constr += [greater(new_p.obj,right)] new_constr += new_p.constr # Return constraints return cvxpy_list(new_constr)
def __call__(self, *args): """ Description ----------- Call program with specified arguments. Parameters are substituted with arguments. If all arguments are numeric, the resulting optimization program is solved. If some arguments are object, a tree is returned. Arguments --------- args: List of arguments. (Can be numbers or objects) """ # Process input if (len(args) != 0 and type(args[0]) is list): args = args[0] # Create argument list arg_list = [] for arg in args: if (np.isscalar(arg) or type(arg).__name__ in SCALAR_OBJS): arg_list += [arg] elif (type(arg) is cvxpy_matrix or type(arg).__name__ in ARRAY_OBJS): (m, n) = arg.shape for i in range(0, m, 1): for j in range(0, n, 1): arg_list += [arg[i, j]] else: raise ValueError('Invalid argument type') # Check number of arguments if (len(arg_list) != len(self.params)): raise ValueError('Invalid argument syntax') # Solve if numeric if (len(arg_list) == 0 or reduce(lambda x, y: x and y, map(lambda x: np.isscalar(x), arg_list))): # Substitute parameters with arguments p1_map = {} for k in range(0, len(arg_list), 1): p1_map[self.params[k]] = arg_list[k] new_p = prog((self.action, re_eval(self.obj, p1_map)), re_eval(self.constr, p1_map), [], self.options, self.name) # Solve program obj, lagrange_mul_eq = solve_prog(new_p) return obj # Upgrade numbers to objects for i in range(0, len(arg_list), 1): if (np.isscalar(arg_list[i])): arg_list[i] = cvxpy_obj(CONSTANT, arg_list[i], str(arg_list[i])) # Return tree return cvxpy_tree(self, arg_list)
def scp(p,bad_constr,sol): """ Description: Sequential convex programming algorithm. Solve program p and try to enforce equality on the bad constraints. Argument p: cvxpy_program is assumed to be in expanded and equivalent format. Argument bad_constr: List of nonconvex constraints, also in expanded and equivalent format. Argument sol: Solution of relaxation. """ # Quit if program is linear if(len(bad_constr) == 0): if(not p.options['quiet']): print 'Tightening not needed' return True # Get parameters tight_tol = p.options['SCP_ALG']['tight tol'] starting_lambda = p.options['SCP_ALG']['starting lambda'] max_scp_iter = p.options['SCP_ALG']['max scp iter'] lambda_multiplier = p.options['SCP_ALG']['lambda multiplier'] max_lambda = p.options['SCP_ALG']['max lambda'] top_residual = p.options['SCP_ALG']['top residual'] # Construct slacks slacks = [abs(c.left-c.right) for c in bad_constr] # Print header if(not p.options['quiet']): print 'Iter\t:', print 'Max Slack\t:', print 'Objective\t:', print 'Solver Status\t:', print 'Pres\t\t:', print 'Dres\t\t:', print 'Lambda Max/Min' # SCP Loop lam = starting_lambda*np.ones(len(slacks)) for i in range(0,max_scp_iter,1): # Calculate max slack max_slack = max(map(lambda x:x.get_value(), slacks)) # Quit if status is primal infeasible if(sol['status'] == 'primal infeasible'): if(not p.options['quiet']): print 'Unable to tighten: Problem became infeasible' return False # Check if dual infeasible if(sol['status'] == 'dual infeasible'): sol['status'] = 'dual inf' sol['primal infeasibility'] = np.NaN sol['dual infeasibility'] = np.NaN # Print values if(not p.options['quiet']): print '%d\t:' %i, print '%.3e\t:' %max_slack, print '%.3e\t:' %p.obj.get_value(), print ' '+sol['status']+'\t:', if(sol['primal infeasibility'] is not np.NaN): print '%.3e\t:' %sol['primal infeasibility'], else: print '%.3e\t\t:' %sol['primal infeasibility'], if(sol['dual infeasibility'] is not np.NaN): print '%.3e\t:' %sol['dual infeasibility'], else: print '%.3e\t\t:' %sol['dual infeasibility'], print '(%.1e,%.1e)' %(np.max(lam),np.min(lam)) # Quit if max slack is small if(max_slack < tight_tol and sol['status'] == 'optimal'): if(not p.options['quiet']): print 'Tightening successful' return True # Quit if residual is too large if(sol['primal infeasibility'] >= top_residual or sol['dual infeasibility'] >= top_residual): if(not p.options['quiet']): print 'Unable to tighten: Residuals are too large' return False # Linearize slacks linear_slacks = [] for c in bad_constr: fn = c.left.item args = c.left.children right = c.right line = fn._linearize(args,right) linear_slacks += [line] # Add linearized slacks to objective sum_lin_slacks = 0.0 for j in range(0,len(slacks),1): sum_lin_slacks += lam[j]*linear_slacks[j] if(p.action == MINIMIZE): new_obj = p.obj + sum_lin_slacks else: new_obj = p.obj - sum_lin_slacks new_t0, obj_constr = expand(new_obj) new_p = prog((p.action,new_t0), obj_constr+p.constr,[],p.options) # Solve new problem sol = solve_convex(new_p,'scp') # Update lambdas for j in range(0,len(slacks),1): if(slacks[j].get_value() >= tight_tol): if(lam[j] < max_lambda): lam[j] = lam[j]*lambda_multiplier # Maxiters reached if(not p.options['quiet']): print 'Unable to tighten: Maximum iterations reached' if(sol['status'] == 'optimal'): return True else: return False
def __call__(self,*args): """ Description ----------- Call program with specified arguments. Parameters are substituted with arguments. If all arguments are numeric, the resulting optimization program is solved. If some arguments are object, a tree is returned. Arguments --------- args: List of arguments. (Can be numbers or objects) """ # Process input if(len(args) != 0 and type(args[0]) is list): args = args[0] # Create argument list arg_list = [] for arg in args: if(np.isscalar(arg) or type(arg).__name__ in SCALAR_OBJS): arg_list += [arg] elif(type(arg) is cvxpy_matrix or type(arg).__name__ in ARRAY_OBJS): (m,n) = arg.shape for i in range(0,m,1): for j in range(0,n,1): arg_list += [arg[i,j]] else: raise ValueError('Invalid argument type') # Check number of arguments if(len(arg_list) != len(self.params)): raise ValueError('Invalid argument syntax') # Solve if numeric if(len(arg_list) == 0 or reduce(lambda x,y: x and y, map(lambda x: np.isscalar(x),arg_list))): # Substitute parameters with arguments p1_map = {} for k in range(0,len(arg_list),1): p1_map[self.params[k]] = arg_list[k] new_p = prog((self.action,re_eval(self.obj,p1_map)), re_eval(self.constr,p1_map),[], self.options,self.name) # Solve program obj,lagrange_mul_eq = solve_prog(new_p) return obj # Upgrade numbers to objects for i in range(0,len(arg_list),1): if(np.isscalar(arg_list[i])): arg_list[i] = cvxpy_obj(CONSTANT, arg_list[i], str(arg_list[i])) # Return tree return cvxpy_tree(self,arg_list)