def norm_inf_canon(expr, args): x = args[0] axis = expr.axis shape = expr.shape t = Variable(shape) if axis is None: # shape = (1, 1) promoted_t = promote(t, x.shape) elif axis == 0: # shape = (1, n) promoted_t = Constant(np.ones( (x.shape[0], 1))) * reshape(t, (1, x.shape[1])) else: # shape = (m, 1) promoted_t = reshape(t, (x.shape[0], 1)) * Constant( np.ones((1, x.shape[1]))) return t, [x <= promoted_t, x + promoted_t >= 0]
def linearize(expr): """Returns the tangent approximation to the expression. Gives an elementwise lower (upper) bound for convex (concave) expressions. No guarantees for non-DCP expressions. Returns None if cannot be linearized. Args: expr: An expression. Returns: An affine expression or None. """ expr = Constant.cast_to_const(expr) if expr.is_affine(): return expr else: tangent = expr.value if tangent is None: raise ValueError( "Cannot linearize non-affine expression with missing variable values." ) grad_map = expr.grad for var in expr.variables(): if grad_map[var] is None: return None elif var.is_matrix(): flattened = Constant(grad_map[var]).T*vec(var - var.value) tangent = tangent + reshape(flattened, *expr.size) else: tangent = tangent + Constant(grad_map[var]).T*(var - var.value) return tangent
def at_least_2D(expr): """Upcast 0D and 1D to 2D. """ if expr.ndim < 2: return reshape(expr, (expr.size, 1)) else: return expr
def linearize(expr): """Returns the tangent approximation to the expression. Gives an elementwise lower (upper) bound for convex (concave) expressions. No guarantees for non-DCP expressions. Returns None if cannot be linearized. Args: expr: An expression. Returns: An affine expression or None. """ expr = Constant.cast_to_const(expr) if expr.is_affine(): return expr else: tangent = expr.value if tangent is None: raise ValueError( "Cannot linearize non-affine expression with missing variable values." ) grad_map = expr.grad for var in expr.variables(): if grad_map[var] is None: return None elif var.is_matrix(): flattened = Constant(grad_map[var]).T * vec(var - var.value) tangent = tangent + reshape(flattened, *expr.size) else: tangent = tangent + Constant( grad_map[var]).T * (var - var.value) return tangent
def apply(self, problem): if not attributes_present(problem.variables(), CONVEX_ATTRIBUTES): return problem, () # For each unique variable, add constraints. id2new_var = {} id2new_obj = {} id2old_var = {} constr = [] for var in problem.variables(): if var.id not in id2new_var: id2old_var[var.id] = var new_var = False new_attr = var.attributes.copy() for key in CONVEX_ATTRIBUTES: if new_attr[key]: new_var = True new_attr[key] = False if attributes_present([var], SYMMETRIC_ATTRIBUTES): n = var.shape[0] shape = (n*(n+1)//2, 1) upper_tri = Variable(shape, var_id=var.id, **new_attr) upper_tri.set_variable_of_provenance(var) id2new_var[var.id] = upper_tri fill_coeff = Constant(upper_tri_to_full(n)) full_mat = fill_coeff @ upper_tri obj = reshape(full_mat, (n, n)) elif var.attributes['diag']: diag_var = Variable(var.shape[0], var_id=var.id, **new_attr) diag_var.set_variable_of_provenance(var) id2new_var[var.id] = diag_var obj = diag(diag_var) elif new_var: obj = Variable(var.shape, var_id=var.id, **new_attr) obj.set_variable_of_provenance(var) id2new_var[var.id] = obj else: obj = var id2new_var[var.id] = obj id2new_obj[id(var)] = obj if var.is_pos() or var.is_nonneg(): constr.append(obj >= 0) elif var.is_neg() or var.is_nonpos(): constr.append(obj <= 0) elif var.is_psd(): constr.append(obj >> 0) elif var.attributes['NSD']: constr.append(obj << 0) # Create new problem. obj = problem.objective.tree_copy(id_objects=id2new_obj) cons_id_map = {} for cons in problem.constraints: constr.append(cons.tree_copy(id_objects=id2new_obj)) cons_id_map[cons.id] = constr[-1].id inverse_data = (id2new_var, id2old_var, cons_id_map) return cvxtypes.problem()(obj, constr), inverse_data
def max_canon(expr, args): x = args[0] shape = expr.shape axis = expr.axis t = Variable(shape) if axis is None: # shape = (1, 1) promoted_t = promote(t, x.shape) elif axis == 0: # shape = (1, n) promoted_t = Constant(np.ones((x.shape[0], 1))) * reshape( t, (1, x.shape[1])) else: # shape = (m, 1) promoted_t = reshape(t, (x.shape[0], 1)) * Constant( np.ones((1, x.shape[1]))) constraints = [x <= promoted_t] return t, constraints
def abs_canon(expr, real_args, imag_args, real2imag): # Imaginary. if real_args[0] is None: output = abs(imag_args[0]) elif imag_args[0] is None: # Real output = abs(real_args[0]) else: # Complex. real = real_args[0].flatten() imag = imag_args[0].flatten() norms = pnorm(vstack([real, imag]), p=2, axis=0) output = reshape(norms, real_args[0].shape) return output, None
def log_sum_exp_canon(expr, args): x = args[0] shape = expr.shape axis = expr.axis t = Variable(shape) # log(sum(exp(x))) <= t <=> sum(exp(x-t)) <= 1 if axis is None: # shape = (1, 1) promoted_t = promote(t, x.shape) elif axis == 0: # shape = (1, n) promoted_t = Constant(np.ones( (x.shape[0], 1))) * reshape(t, (1, ) + x.shape[1:]) else: # shape = (m, 1) promoted_t = reshape(t, x.shape[:-1] + (1, )) * Constant(np.ones((1, x.shape[1]))) exp_expr = exp(x - promoted_t) obj, constraints = exp_canon(exp_expr, exp_expr.args) obj = sum(obj, axis=axis) ones = Constant(np.ones(shape)) constraints.append(obj <= ones) return t, constraints
def matrix_frac_canon(expr, args): X = args[0] # n by m matrix. P = args[1] # n by n matrix. if len(X.shape) == 1: X = reshape(X, (X.shape[0], 1)) n, m = X.shape T = Variable((m, m), symmetric=True) M = bmat([[P, X], [X.T, T]]) # ^ a matrix with Schur complement T - X.T*P^-1*X. constraints = [PSD(M)] if not P.is_symmetric(): ut = upper_tri(P) lt = upper_tri(P.T) constraints.append(ut == lt) return trace(T), constraints
def linearize(expr): """Returns an affine approximation to the expression computed at the variable/parameter values. Gives an elementwise lower (upper) bound for convex (concave) expressions that is tight at the current variable/parameter values. No guarantees for non-DCP expressions. If f and g are convex, the objective f - g can be (heuristically) minimized using the implementation below of the convex-concave method: .. code :: python for iters in range(N): Problem(Minimize(f - linearize(g))).solve() Returns None if cannot be linearized. Args: expr: An expression. Returns: An affine expression or None. """ expr = Constant.cast_to_const(expr) if expr.is_affine(): return expr else: tangent = expr.value if tangent is None: raise ValueError( "Cannot linearize non-affine expression with missing variable values." ) grad_map = expr.grad for var in expr.variables(): if grad_map[var] is None: return None elif var.is_matrix(): flattened = Constant(grad_map[var]).T * vec(var - var.value) tangent = tangent + reshape(flattened, expr.shape) else: tangent = tangent + Constant( grad_map[var]).T * (var - var.value) return tangent
def apply(self, problem): inverse_data = InverseData(problem) new_obj, new_var = self.stuffed_objective(problem, inverse_data) # Form the constraints extractor = CoeffExtractor(inverse_data) new_cons = [] for con in problem.constraints: arg_list = [] for arg in con.args: A, b = extractor.get_coeffs(arg) arg_list.append(reshape(A * new_var + b, arg.shape)) new_cons.append(con.copy(arg_list)) inverse_data.cons_id_map[con.id] = new_cons[-1].id # Map of old constraint id to new constraint id. inverse_data.minimize = type(problem.objective) == Minimize new_prob = problems.problem.Problem(Minimize(new_obj), new_cons) return new_prob, inverse_data
def matrix_frac_canon(expr, args): X = args[0] # n by m matrix. P = args[1] # n by n matrix. if len(X.shape) == 1: X = reshape(X, (X.shape[0], 1)) n, m = X.shape # Create a matrix with Schur complement T - X.T*P^-1*X. M = Variable((n + m, n + m), PSD=True) T = Variable((m, m)) constraints = [] # Fix M using the fact that P must be affine by the DCP rules. # M[0:n, 0:n] == P. constraints.append(M[0:n, 0:n] == P) # M[0:n, n:n+m] == X constraints.append(M[0:n, n:n + m] == X) # M[n:n+m, n:n+m] == T constraints.append(M[n:n + m, n:n + m] == T) return trace(T), constraints
def apply(self, problem): """See docstring for MatrixStuffing.apply""" inverse_data = InverseData(problem) # Form the constraints extractor = CoeffExtractor(inverse_data) new_obj, new_var, r = self.stuffed_objective(problem, extractor) inverse_data.r = r # Lower equality and inequality to Zero and NonPos. cons = [] for con in problem.constraints: if isinstance(con, Equality): con = lower_equality(con) elif isinstance(con, Inequality): con = lower_inequality(con) cons.append(con) # Batch expressions together, then split apart. expr_list = [arg for c in cons for arg in c.args] problem_data_tensor = extractor.affine(expr_list) Afull, bfull = canon.get_matrix_and_offset_from_unparameterized_tensor( problem_data_tensor, new_var.size) if 0 not in Afull.shape and 0 not in bfull.shape: Afull = cvxtypes.constant()(Afull) bfull = cvxtypes.constant()(np.atleast_1d(bfull)) new_cons = [] offset = 0 for orig_con, con in zip(problem.constraints, cons): arg_list = [] for arg in con.args: A = Afull[offset:offset + arg.size, :] b = bfull[offset:offset + arg.size] arg_list.append(reshape(A @ new_var + b, arg.shape)) offset += arg.size new_constraint = con.copy(arg_list) new_cons.append(new_constraint) inverse_data.constraints = new_cons inverse_data.minimize = type(problem.objective) == Minimize new_prob = problems.problem.Problem(Minimize(new_obj), new_cons) return new_prob, inverse_data
def soc_canon(expr, real_args, imag_args, real2imag): # Imaginary. if real_args[1] is None: output = [SOC(real_args[0], imag_args[1], axis=expr.axis, constr_id=real2imag[expr.id])] elif imag_args[1] is None: # Real output = [SOC(real_args[0], real_args[1], axis=expr.axis, constr_id=expr.id)] else: # Complex. orig_shape = real_args[1].shape real = real_args[1].flatten() imag = imag_args[1].flatten() flat_X = Variable(real.shape) inner_SOC = SOC(flat_X, vstack([real, imag]), axis=0) real_X = reshape(flat_X, orig_shape) outer_SOC = SOC(real_args[0], real_X, axis=expr.axis, constr_id=expr.id) output = [inner_SOC, outer_SOC] return output, None
def apply(self, problem): inverse_data = InverseData(problem) # Form the constraints extractor = CoeffExtractor(inverse_data) new_obj, new_var, r = self.stuffed_objective(problem, extractor) inverse_data.r = r # Lower equality and inequality to Zero and NonPos. cons = [] for con in problem.constraints: if isinstance(con, Equality): con = lower_equality(con) elif isinstance(con, Inequality): con = lower_inequality(con) elif isinstance(con, SOC) and con.axis == 1: con = SOC(con.args[0], con.args[1].T, axis=0, constr_id=con.constr_id) cons.append(con) # Batch expressions together, then split apart. expr_list = [arg for con in cons for arg in con.args] Afull, bfull = extractor.affine(expr_list) new_cons = [] offset = 0 for con in cons: arg_list = [] for arg in con.args: A = Afull[offset:offset + arg.size, :] b = bfull[offset:offset + arg.size] arg_list.append(reshape(A * new_var + b, arg.shape)) offset += arg.size new_cons.append(con.copy(arg_list)) inverse_data.cons_id_map[con.id] = new_cons[-1].id # Map of old constraint id to new constraint id. inverse_data.minimize = type(problem.objective) == Minimize new_prob = problems.problem.Problem(Minimize(new_obj), new_cons) return new_prob, inverse_data
def apply(self, problem): """Returns a stuffed problem. The returned problem is a minimization problem in which every constraint in the problem has affine arguments that are expressed in the form A @ x + b. Parameters ---------- problem: The problem to stuff; the arguments of every constraint must be affine constraints: A list of constraints, whose arguments are affine Returns ------- Problem The stuffed problem InverseData Data for solution retrieval """ inverse_data = InverseData(problem) # Form the constraints extractor = CoeffExtractor(inverse_data) new_obj, new_var, r = self.stuffed_objective(problem, extractor) inverse_data.r = r # Lower equality and inequality to Zero and NonPos. cons = [] for con in problem.constraints: if isinstance(con, Equality): con = lower_equality(con) elif isinstance(con, Inequality): con = lower_inequality(con) elif isinstance(con, SOC) and con.axis == 1: con = SOC(con.args[0], con.args[1].T, axis=0, constr_id=con.constr_id) cons.append(con) # Batch expressions together, then split apart. expr_list = [arg for c in cons for arg in c.args] Afull, bfull = extractor.affine(expr_list) if 0 not in Afull.shape and 0 not in bfull.shape: Afull = cvxtypes.constant()(Afull) bfull = cvxtypes.constant()(bfull) new_cons = [] offset = 0 for con in cons: arg_list = [] for arg in con.args: A = Afull[offset:offset+arg.size, :] b = bfull[offset:offset+arg.size] arg_list.append(reshape(A*new_var + b, arg.shape)) offset += arg.size new_cons.append(con.copy(arg_list)) # Map old constraint id to new constraint id. inverse_data.cons_id_map[con.id] = new_cons[-1].id inverse_data.minimize = type(problem.objective) == Minimize new_prob = problems.problem.Problem(Minimize(new_obj), new_cons) return new_prob, inverse_data