def graph_implementation(self, arg_objs, shape: Tuple[int, ...], data=None) -> Tuple[lo.LinOp, List[Constraint]]: """Index/slice into the expression. Parameters ---------- arg_objs : list LinExpr for each argument. shape : tuple The shape of the resulting expression. data : tuple A tuple of slices. Returns ------- tuple (LinOp, [constraints]) """ select_mat = self._select_mat final_shape = self._select_mat.shape select_vec = np.reshape(select_mat, select_mat.size, order='F') # Select the chosen entries from expr. arg = arg_objs[0] identity = sp.eye(self.args[0].size).tocsc() vec_arg = lu.reshape(arg, (self.args[0].size, )) mul_mat = identity[select_vec] mul_const = lu.create_const(mul_mat, mul_mat.shape, sparse=True) mul_expr = lu.mul_expr(mul_const, vec_arg, (mul_mat.shape[0], )) obj = lu.reshape(mul_expr, final_shape) return (obj, [])
def graph_implementation(self, arg_objs, shape: Tuple[int, ...], data=None): """Reshape Parameters ---------- arg_objs : list LinExpr for each argument. shape : tuple The shape of the resulting expression. data : Additional data required by the atom. Returns ------- tuple (LinOp for objective, list of constraints) """ arg = arg_objs[0] if data[1] == 'F': return (lu.reshape(arg, shape), []) else: # 'C': arg = lu.transpose(arg) if len(shape) <= 1: return (lu.reshape(arg, shape), []) else: result = lu.reshape(arg, (shape[1], shape[0])) return (lu.transpose(result), [])
def gm(t, x, y): length = t.size[0] * t.size[1] return SOC_Axis( lu.reshape(lu.sum_expr([x, y]), (length, 1)), lu.vstack([ lu.reshape(lu.sub_expr(x, y), (1, length)), lu.reshape(lu.mul_expr(two, t, t.size), (1, length)) ], (2, length)), 0)
def gm(t, x, y): length = t.size[0]*t.size[1] return SOC_Axis(lu.reshape(lu.sum_expr([x, y]), (length, 1)), lu.vstack([ lu.reshape(lu.sub_expr(x, y), (1, length)), lu.reshape(lu.mul_expr(two, t, t.size), (1, length)) ], (2, length)), 0)
def _scaled_lower_tri(self): """Returns a LinOp representing the lower triangular entries. Scales the strictly lower triangular entries by sqrt(2) as required by SCS. """ rows = cols = self.size[0] entries = rows*(cols + 1)//2 val_arr = [] row_arr = [] col_arr = [] count = 0 for j in range(cols): for i in range(rows): if j <= i: # Index in the original matrix. col_arr.append(j*rows + i) # Index in the extracted vector. row_arr.append(count) if j == i: val_arr.append(1.0) else: val_arr.append(np.sqrt(2)) count += 1 size = (entries, rows*cols) coeff = sp.coo_matrix((val_arr, (row_arr, col_arr)), size).tocsc() coeff = lu.create_const(coeff, size, sparse=True) vect = lu.reshape(self.A, (rows*cols, 1)) return lu.mul_expr(coeff, vect, (entries, 1))
def format_axis(t, X, axis): """Formats all the row/column cones for the solver. Parameters ---------- t: The scalar part of the second-order constraint. X: A matrix whose rows/columns are each a cone. axis: Slice by column 0 or row 1. Returns ------- list A list of LinLeqConstr that represent all the elementwise cones. """ # Reduce to norms of columns. if axis == 1: X = lu.transpose(X) # Create matrices Tmat, Xmat such that Tmat*t + Xmat*X # gives the format for the elementwise cone constraints. cone_size = 1 + X.shape[0] terms = [] # Make t_mat mat_shape = (cone_size, 1) t_mat = sp.coo_matrix(([1.0], ([0], [0])), mat_shape).tocsc() t_mat = lu.create_const(t_mat, mat_shape, sparse=True) t_vec = t if not t.shape: # t is scalar t_vec = lu.reshape(t, (1, 1)) else: # t is 1D t_vec = lu.reshape(t, (1, t.shape[0])) mul_shape = (cone_size, t_vec.shape[1]) terms += [lu.mul_expr(t_mat, t_vec, mul_shape)] # Make X_mat if len(X.shape) == 1: X = lu.reshape(X, (X.shape[0], 1)) mat_shape = (cone_size, X.shape[0]) val_arr = (cone_size - 1) * [1.0] row_arr = list(range(1, cone_size)) col_arr = list(range(cone_size - 1)) X_mat = sp.coo_matrix((val_arr, (row_arr, col_arr)), mat_shape).tocsc() X_mat = lu.create_const(X_mat, mat_shape, sparse=True) mul_shape = (cone_size, X.shape[1]) terms += [lu.mul_expr(X_mat, X, mul_shape)] return [lu.create_geq(lu.sum_expr(terms))]
def canonicalize(self): """Variable must be semidefinite and symmetric. """ upper_tri = lu.create_var((self.size[0], 1), self.id) fill_coeff = upper_tri_to_full(self.n) fill_coeff = lu.create_const(fill_coeff, (self.n*self.n, self.size[0]), sparse=True) full_mat = lu.mul_expr(fill_coeff, upper_tri, (self.n*self.n, 1)) full_mat = lu.reshape(full_mat, (self.n, self.n)) return (upper_tri, [SDP(full_mat, enforce_sym=False)])
def graph_implementation(arg_objs, size, data=None): """Convolve two vectors. 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) """ return (lu.reshape(arg_objs[0], size), [])
def combine_lin_ops(operators): """Combines the LinOps by stacking their output into a vector. Parameters ---------- operators : list A list of LinOps. Returns ------- LinOp The combined LinOp. """ # First vectorize all the LinOp outputs. vect_lin_ops = [] total_length = 0 for lin_op in operators: if lin_op.size[1] != 1: new_size = (lin_op.size[0] * lin_op.size[1], 1) lin_op = lu.reshape(lin_op, new_size) vect_lin_ops.append(lin_op) total_length += lin_op.size[0] # Stack all the outputs into a single vector. return lu.vstack(vect_lin_ops, (total_length, 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] axis = data[1] 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: if axis is None: return t, [SOC(t, [x])] else: t = lu.create_var(size) return t, [ SOC_Axis(lu.reshape(t, (t.size[0] * t.size[1], 1)), x, axis) ] 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 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] axis = data[1] 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: if axis is None: return t, [SOC(t, [x])] else: t = lu.create_var(size) return t, [SOC_Axis(lu.reshape(t, (t.size[0]*t.size[1], 1)), x, axis)] 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