def test_soc_constraint(self): exp = self.x + self.z scalar_exp = self.a + self.b constr = SOC(scalar_exp, exp) self.assertEqual(constr.size, 3) # Test invalid dimensions. error_str = ("Argument dimensions (1,) and (1, 4), with axis=0, " "are incompatible.") with self.assertRaises(Exception) as cm: SOC(Variable(1), Variable((1, 4))) self.assertEqual(str(cm.exception), error_str)
def gm(t, x, y): length = t.size return SOC(t=reshape(x + y, (length, )), X=vstack( [reshape(x - y, (1, length)), reshape(2 * t, (1, length))]), axis=0)
def set_affine_constraints(G, h, y, K_aff): constraints = [] i = 0 if K_aff[a2d.ZERO]: dim = K_aff[a2d.ZERO] constraints.append(G[i:i + dim, :] @ y == h[i:i + dim]) i += dim if K_aff[a2d.NONNEG]: dim = K_aff[a2d.NONNEG] constraints.append(G[i:i + dim, :] @ y <= h[i:i + dim]) i += dim for dim in K_aff[a2d.SOC]: expr = h[i:i + dim] - G[i:i + dim, :] @ y constraints.append(SOC(expr[0], expr[1:])) i += dim if K_aff[a2d.EXP]: dim = 3 * K_aff[a2d.EXP] expr = cp.reshape(h[i:i + dim] - G[i:i + dim, :] @ y, (dim // 3, 3), order='C') constraints.append(ExpCone(expr[:, 0], expr[:, 1], expr[:, 2])) i += dim if K_aff[a2d.POW3D]: alpha = np.array(K_aff[a2d.POW3D]) expr = cp.reshape(h[i:] - G[i:, :] @ y, (alpha.size, 3), order='C') constraints.append( PowCone3D(expr[:, 0], expr[:, 1], expr[:, 2], alpha)) return constraints
def graph_implementation(arg_objs, size, data=None): """Reduces the atom to an affine expression and list of constraints. Parameters ---------- arg_objs : list LinExpr for each argument. size : tuple The size of the resulting expression. data : Additional data required by the atom. Returns ------- tuple (LinOp for objective, list of constraints) """ x = arg_objs[0] y = arg_objs[1] # Known to be a scalar. v = lu.create_var((1, 1)) two = lu.create_const(2, (1, 1)) constraints = [ SOC(lu.sum_expr([y, v]), [lu.sub_expr(y, v), lu.mul_expr(two, x, x.size)]), lu.create_geq(y) ] return (v, constraints)
def pnorm_canon(expr, args): x = args[0] p = expr.p axis = expr.axis shape = expr.shape t = Variable(shape) if p == 2: if axis is None: assert shape == tuple() return t, [SOC(t, vec(x))] else: return t, [SOC(vec(t), x, axis)] # we need an absolute value constraint for the symmetric convex branches # (p > 1) constraints = [] if p > 1: # TODO(akshayka): Express this more naturally (recursively), in terms # of the other atoms abs_expr = abs(x) abs_x, abs_constraints = abs_canon(abs_expr, abs_expr.args) x = abs_x constraints += abs_constraints # now, we take care of the remaining convex and concave branches # to create the rational powers, we need a new variable, r, and # the constraint sum(r) == t r = Variable(x.shape) constraints += [sum(r) == t] # todo: no need to run gm_constr to form the tree each time. # we only need to form the tree once promoted_t = Constant(np.ones(x.shape)) * t p = Fraction(p) if p < 0: constraints += gm_constrs(promoted_t, [x, r], (-p / (1 - p), 1 / (1 - p))) if 0 < p < 1: constraints += gm_constrs(r, [x, promoted_t], (p, 1 - p)) if p > 1: constraints += gm_constrs(x, [r, promoted_t], (1 / p, 1 - 1 / p)) return t, constraints
def quad_over_lin_canon(expr, args): # quad_over_lin := sum_{ij} X^2_{ij} / y x = args[0] y = args[1].flatten() # precondition: shape == () t = Variable(1,) # (y+t, y-t, 2*x) must lie in the second-order cone, # where y+t is the scalar part of the second-order # cone constraint. constraints = [SOC( t=y+t, X=hstack([y-t, 2*x.flatten()]), axis=0 ), y >= 0] return t, constraints
def simulate_chain(in_prob): # Get a ParamConeProg object reductions = [Dcp2Cone(), CvxAttr2Constr(), ConeMatrixStuffing()] chain = Chain(None, reductions) cone_prog, inv_prob2cone = chain.apply(in_prob) # Dualize the problem, reconstruct a high-level cvxpy problem for the dual. # Solve the problem, invert the dualize reduction. solver = ConicSolver() cone_prog = solver.format_constraints(cone_prog, exp_cone_order=[0, 1, 2]) data, inv_data = a2d.Dualize.apply(cone_prog) A, b, c, K_dir = data[s.A], data[s.B], data[s.C], data['K_dir'] y = cp.Variable(shape=(A.shape[1], )) constraints = [A @ y == b] i = K_dir[a2d.FREE] dual_prims = {a2d.FREE: y[:i], a2d.SOC: []} if K_dir[a2d.NONNEG]: dim = K_dir[a2d.NONNEG] dual_prims[a2d.NONNEG] = y[i:i + dim] constraints.append(y[i:i + dim] >= 0) i += dim for dim in K_dir[a2d.SOC]: dual_prims[a2d.SOC].append(y[i:i + dim]) constraints.append(SOC(y[i], y[i + 1:i + dim])) i += dim if K_dir[a2d.DUAL_EXP]: dual_prims[a2d.DUAL_EXP] = y[i:] y_de = cp.reshape(y[i:], ((y.size - i) // 3, 3), order='C') # fill rows first constraints.append( ExpCone(-y_de[:, 1], -y_de[:, 0], np.exp(1) * y_de[:, 2])) objective = cp.Maximize(c @ y) dual_prob = cp.Problem(objective, constraints) dual_prob.solve(solver='SCS', eps=1e-8) dual_prims[a2d.FREE] = dual_prims[a2d.FREE].value if K_dir[a2d.NONNEG]: dual_prims[a2d.NONNEG] = dual_prims[a2d.NONNEG].value dual_prims[a2d.SOC] = [expr.value for expr in dual_prims[a2d.SOC]] if K_dir[a2d.DUAL_EXP]: dual_prims[a2d.DUAL_EXP] = dual_prims[a2d.DUAL_EXP].value dual_duals = {s.EQ_DUAL: constraints[0].dual_value} dual_sol = cp.Solution(dual_prob.status, dual_prob.value, dual_prims, dual_duals, dict()) cone_sol = a2d.Dualize.invert(dual_sol, inv_data) # Pass the solution back up the solving chain. in_prob_sol = chain.invert(cone_sol, inv_prob2cone) in_prob.unpack(in_prob_sol)
def set_direct_constraints(y, K_dir): constraints = [] i = K_dir[a2d.FREE] if K_dir[a2d.NONNEG]: dim = K_dir[a2d.NONNEG] constraints.append(y[i:i + dim] >= 0) i += dim for dim in K_dir[a2d.SOC]: constraints.append(SOC(y[i], y[i + 1:i + dim])) i += dim if K_dir[a2d.EXP]: dim = y.size - i expr = cp.reshape(y[i:], (dim // 3, 3), order='C') constraints.append(ExpCone(expr[:, 0], expr[:, 1], expr[:, 2])) return constraints
def set_affine_constraints(G, h, y, K_aff): constraints = [] i = 0 if K_aff[a2d.ZERO]: dim = K_aff[a2d.ZERO] constraints.append(G[i:i + dim, :] @ y == h[i:i + dim]) i += dim if K_aff[a2d.NONNEG]: dim = K_aff[a2d.NONNEG] constraints.append(G[i:i + dim, :] @ y <= h[i:i + dim]) i += dim for dim in K_aff[a2d.SOC]: expr = h[i:i + dim] - G[i:i + dim, :] @ y constraints.append(SOC(expr[0], expr[1:])) i += dim if K_aff[a2d.EXP]: dim = G.shape[0] - i expr = cp.reshape(h[i:] - G[i:, :] @ y, (dim // 3, 3), order='C') constraints.append(ExpCone(expr[:, 0], expr[:, 1], expr[:, 2])) return constraints
def graph_implementation(arg_objs, size, data=None): """Reduces the atom to an affine expression and list of constraints. Parameters ---------- arg_objs : list LinExpr for each argument. size : tuple The size of the resulting expression. data : Additional data required by the atom. Returns ------- tuple (LinOp for objective, list of constraints) """ x = arg_objs[0] t = lu.create_var((1, 1)) return (t, [SOC(t, [x])])
def set_direct_constraints(y, K_dir): constraints = [] i = K_dir[a2d.FREE] if K_dir[a2d.NONNEG]: dim = K_dir[a2d.NONNEG] constraints.append(y[i:i + dim] >= 0) i += dim for dim in K_dir[a2d.SOC]: constraints.append(SOC(y[i], y[i + 1:i + dim])) i += dim if K_dir[a2d.EXP]: dim = 3 * K_dir[a2d.EXP] expr = cp.reshape(y[i:i + dim], (dim // 3, 3), order='C') constraints.append(ExpCone(expr[:, 0], expr[:, 1], expr[:, 2])) i += dim if K_dir[a2d.POW3D]: alpha = np.array(K_dir[a2d.POW3D]) expr = cp.reshape(y[i:], (alpha.size, 3), order='C') constraints.append( PowCone3D(expr[:, 0], expr[:, 1], expr[:, 2], alpha)) return constraints
def test_soc_constraint(self): exp = self.x + self.z scalar_exp = self.a + self.b constr = SOC(scalar_exp, [exp]) self.assertEqual(constr.size, (3, 1))
def graph_implementation(arg_objs, size, data=None): r"""Reduces the atom to an affine expression and list of constraints. Parameters ---------- arg_objs : list LinExpr for each argument. size : tuple The size of the resulting expression. data : Additional data required by the atom. Returns ------- tuple (LinOp for objective, list of constraints) Notes ----- Implementation notes. - For general :math:`p \geq 1`, the inequality :math:`\|x\|_p \leq t` is equivalent to the following convex inequalities: .. math:: |x_i| &\leq r_i^{1/p} t^{1 - 1/p}\\ \sum_i r_i &= t. These inequalities happen to also be correct for :math:`p = +\infty`, if we interpret :math:`1/\infty` as :math:`0`. - For general :math:`0 < p < 1`, the inequality :math:`\|x\|_p \geq t` is equivalent to the following convex inequalities: .. math:: r_i &\leq x_i^{p} t^{1 - p}\\ \sum_i r_i &= t. - For general :math:`p < 0`, the inequality :math:`\|x\|_p \geq t` is equivalent to the following convex inequalities: .. math:: t &\leq x_i^{-p/(1-p)} r_i^{1/(1 - p)}\\ \sum_i r_i &= t. Although the inequalities above are correct, for a few special cases, we can represent the p-norm more efficiently and with fewer variables and inequalities. - For :math:`p = 1`, we use the representation .. math:: x_i &\leq r_i\\ -x_i &\leq r_i\\ \sum_i r_i &= t - For :math:`p = \infty`, we use the representation .. math:: x_i &\leq t\\ -x_i &\leq t Note that we don't need the :math:`r` variable or the sum inequality. - For :math:`p = 2`, we use the natural second-order cone representation .. math:: \|x\|_2 \leq t Note that we could have used the set of inequalities given above if we wanted an alternate decomposition of a large second-order cone into into several smaller inequalities. """ p = data[0] x = arg_objs[0] t = lu.create_var((1, 1)) constraints = [] # first, take care of the special cases of p = 2, inf, and 1 if p == 2: return t, [SOC(t, [x])] if p == np.inf: t_ = lu.promote(t, x.size) return t, [lu.create_leq(x, t_), lu.create_geq(lu.sum_expr([x, t_]))] # we need an absolute value constraint for the symmetric convex branches (p >= 1) # we alias |x| as x from this point forward to make the code pretty :) if p >= 1: absx = lu.create_var(x.size) constraints += [lu.create_leq(x, absx), lu.create_geq(lu.sum_expr([x, absx]))] x = absx if p == 1: return lu.sum_entries(x), constraints # now, we take care of the remaining convex and concave branches # to create the rational powers, we need a new variable, r, and # the constraint sum(r) == t r = lu.create_var(x.size) t_ = lu.promote(t, x.size) constraints += [lu.create_eq(lu.sum_entries(r), t)] # make p a fraction so that the input weight to gm_constrs # is a nice tuple of fractions. p = Fraction(p) if p < 0: constraints += gm_constrs(t_, [x, r], (-p / (1 - p), 1 / (1 - p))) if 0 < p < 1: constraints += gm_constrs(r, [x, t_], (p, 1 - p)) if p > 1: constraints += gm_constrs(x, [r, t_], (1 / p, 1 - 1 / p)) return t, constraints
def test_soc_constraint(self): exp = self.x + self.z scalar_exp = self.a + self.b constr = SOC(scalar_exp, [exp]) self.assertEqual(constr.size[0], (3,1)) self.assertEqual(len(constr.format()), 2)
def test_sdc_constraint(self): exp = self.x + self.z scalar_exp = self.a + self.b constr = SOC(exp, scalar_exp) self.assertEqual(constr.size, (3,1)) self.assertEqual(len(constr.format()), 2)
def test_sdc_constraint(self): exp = self.x + self.z scalar_exp = self.a + self.b constr = SOC(scalar_exp, [exp]) self.assertEqual(constr.size, (3, 1)) self.assertEqual(len(constr.format()), 2)
def graph_implementation(arg_objs, size, data=None): r"""Reduces the atom to an affine expression and list of constraints. Parameters ---------- arg_objs : list LinExpr for each argument. size : tuple The size of the resulting expression. data : Additional data required by the atom. Returns ------- tuple (LinOp for objective, list of constraints) Notes ----- Implementation notes. For general ``p``, the p-norm is equivalent to the following convex inequalities: .. math:: x_i &\leq r_i\\ -x_i &\leq r_i\\ r_i &\leq s_i^{1/p} t^{1 - 1/p}\\ \sum_i s_i &\leq t, where :math:`p \geq 1`. These inequalities are also correct for :math:`p = +\infty` if we interpret :math:`1/\infty` as :math:`0`. Although the inequalities above are correct, for a few special cases, we can represent the p-norm more efficiently and with fewer variables and inequalities. - For :math:`p = 1`, we use the representation .. math:: x_i &\leq r_i\\ -x_i &\leq r_i\\ \sum_i r_i &\leq t - For :math:`p = \infty`, we use the representation .. math:: x_i &\leq t\\ -x_i &\leq t Note that we don't need the :math:`s` variables or the sum inequality. - For :math:`p = 2`, we use the natural second-order cone representation .. math:: \|x\|_2 \leq t Note that we could have used the set of inequalities given above if we wanted an alternate decomposition of a large second-order cone into into several smaller inequalities. """ p, w = data x = arg_objs[0] t = None # dummy value so linter won't complain about initialization if p != 1: t = lu.create_var((1, 1)) if p == 2: return t, [SOC(t, [x])] if p == np.inf: r = lu.promote(t, x.size) else: r = lu.create_var(x.size) constraints = [lu.create_geq(lu.sum_expr([x, r])), lu.create_leq(x, r)] if p == 1: return lu.sum_entries(r), constraints if p == np.inf: return t, constraints # otherwise do case of general p s = lu.create_var(x.size) # todo: no need to run gm_constr to form the tree each time. we only need to form the tree once constraints += gm_constrs(r, [s, lu.promote(t, x.size)], w) constraints += [lu.create_leq(lu.sum_entries(s), t)] return t, constraints