def __add__(self, other): """Expression : Sum two expressions. """ if isinstance(other, cvxtypes.constant()) and other.is_zero(): return self self, other = self.broadcast(self, other) return cvxtypes.add_expr()([self, other])
def __mul__(self, other): """The product of two expressions. """ # Multiplying by a constant on the right is handled differently # from multiplying by a constant on the left. if self.is_constant(): # TODO HACK catch c.T*x where c is a NumPy 1D array. if self.size[0] == other.size[0] and \ self.size[1] != self.size[0] and \ isinstance(self, cvxtypes.constant()) and self.is_1D_array: self = self.T return cvxtypes.mul_expr()(self, other) elif other.is_constant(): # Having the constant on the left is more efficient. if self.is_scalar() or other.is_scalar(): return cvxtypes.mul_expr()(other, self) else: return cvxtypes.rmul_expr()(self, other) # When both expressions are not constant # Allow affine * affine but raise DCPError otherwise # Cannot multiply two non-constant expressions. elif self.is_affine() and other.is_affine(): warnings.warn("Forming a nonconvex expression (affine)*(affine).") return cvxtypes.affine_prod_expr()(self, other) else: raise DCPError("Cannot multiply %s and %s." % (self.curvature, other.curvature))
def __init__(self, x, p, max_denom: int = 1024) -> None: self._p_orig = p # NB: It is important that the exponent is an attribute, not # an argument. This prevents parametrized exponents from being replaced # with their logs in Dgp2Dcp. self.p = cvxtypes.expression().cast_to_const(p) if not (isinstance(self.p, cvxtypes.constant()) or isinstance(self.p, cvxtypes.parameter())): raise ValueError( "The exponent `p` must be either a Constant or " "a Parameter; received ", type(p)) self.max_denom = max_denom self.p_rational = None if isinstance(self.p, cvxtypes.constant()): # Compute a rational approximation to p, for DCP (DGP doesn't need # an approximation). if not isinstance(self._p_orig, cvxtypes.expression()): # converting to a CVXPY Constant loses the dtype (eg, int), # so fetch the original exponent when possible p = self._p_orig else: p = self.p.value # how we convert p to a rational depends on the branch of the function if p > 1: p, w = pow_high(p, max_denom) elif 0 < p < 1: p, w = pow_mid(p, max_denom) elif p < 0: p, w = pow_neg(p, max_denom) # note: if, after making the rational approximation, p ends up # being 0 or 1, we default to using the 0 or 1 behavior of the # atom, which affects the curvature, domain, etc... maybe # unexpected behavior to the user if they put in 1.00001? if p == 1: # in case p is a fraction equivalent to 1 p = 1 w = None if p == 0: p = 0 w = None self.p_rational, self.w = p, w self.approx_error = float(abs(self.p_rational - p)) super(power, self).__init__(x)
def is_log_log_constant(self): """Is the expression log-log constant, ie, elementwise positive? """ if not self.is_constant(): return False if isinstance(self, (cvxtypes.constant(), cvxtypes.parameter())): return self.is_pos() else: return self.value is not None and np.all(self.value > 0)
def cast_to_const(expr): """Converts a non-Expression to a Constant. """ if isinstance(expr, list): for elem in expr: if isinstance(elem, Expression): raise ValueError( "The input must be a single CVXPY Expression, not a list. " "Combine Expressions using atoms such as bmat, hstack, and vstack." ) return expr if isinstance(expr, Expression) else cvxtypes.constant()(expr)
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 __radd__(self, other): """Expression : Sum two expressions. """ if isinstance(other, cvxtypes.constant()) and other.is_zero(): return self return other + self
def cast_to_const(expr): """Converts a non-Expression to a Constant. """ return expr if isinstance(expr, Expression) else cvxtypes.constant()(expr)
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
def _is_const(p) -> bool: return isinstance(p, cvxtypes.constant())