1""" 2Copyright, the CVXPY authors 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15""" 16 17import numpy as np 18 19# Only need Variable from expressions, but that would create a circular import. 20from cvxpy.constraints.constraint import Constraint 21from cvxpy.utilities import scopes 22 23 24class NonPos(Constraint): 25 """A constraint of the form :math:`x \\leq 0`. 26 27 The preferred way of creating a ``NonPos`` constraint is through 28 operator overloading. To constrain an expression ``x`` to be non-positive, 29 simply write ``x <= 0``; to constrain ``x`` to be non-negative, write 30 ``x >= 0``. The former creates a ``NonPos`` constraint with ``x`` 31 as its argument, while the latter creates one with ``-x`` as its argument. 32 Strict inequalities are not supported, as they do not make sense in a 33 numerical setting. 34 35 Parameters 36 ---------- 37 expr : Expression 38 The expression to constrain. 39 constr_id : int 40 A unique id for the constraint. 41 """ 42 def __init__(self, expr, constr_id=None) -> None: 43 super(NonPos, self).__init__([expr], constr_id) 44 45 def name(self) -> str: 46 return "%s <= 0" % self.args[0] 47 48 def is_dcp(self, dpp: bool = False) -> bool: 49 """A non-positive constraint is DCP if its argument is convex.""" 50 if dpp: 51 with scopes.dpp_scope(): 52 return self.args[0].is_convex() 53 return self.args[0].is_convex() 54 55 def is_dgp(self, dpp: bool = False) -> bool: 56 return False 57 58 def is_dqcp(self) -> bool: 59 return self.args[0].is_quasiconvex() 60 61 @property 62 def residual(self): 63 """The residual of the constraint. 64 65 Returns 66 --------- 67 NumPy.ndarray 68 """ 69 if self.expr.value is None: 70 return None 71 return np.maximum(self.expr.value, 0) 72 73 def violation(self): 74 res = self.residual 75 if res is None: 76 raise ValueError("Cannot compute the violation of an constraint " 77 "whose expression is None-valued.") 78 viol = np.linalg.norm(res, ord=2) 79 return viol 80 81 82class NonNeg(Constraint): 83 """A constraint of the form :math:`x \\geq 0`. 84 85 This class was created to account for the fact that the 86 ConicSolver interface returns matrix data stated with respect 87 to the nonnegative orthant, rather than the nonpositive orthant. 88 89 This class can be removed if the behavior of ConicSolver is 90 changed. However the current behavior of ConicSolver means 91 CVXPY's dual variable and Lagrangian convention follows the 92 most common convention in the literature. 93 94 Parameters 95 ---------- 96 expr : Expression 97 The expression to constrain. 98 constr_id : int 99 A unique id for the constraint. 100 """ 101 def __init__(self, expr, constr_id=None) -> None: 102 super(NonNeg, self).__init__([expr], constr_id) 103 104 def name(self) -> str: 105 return "0 <= %s" % self.args[0] 106 107 def is_dcp(self, dpp: bool = False) -> bool: 108 """A non-negative constraint is DCP if its argument is concave.""" 109 if dpp: 110 with scopes.dpp_scope(): 111 return self.args[0].is_concave() 112 return self.args[0].is_concave() 113 114 def is_dgp(self, dpp: bool = False) -> bool: 115 return False 116 117 def is_dqcp(self) -> bool: 118 return self.args[0].is_quasiconcave() 119 120 @property 121 def residual(self): 122 """The residual of the constraint. 123 124 Returns 125 --------- 126 NumPy.ndarray 127 """ 128 if self.expr.value is None: 129 return None 130 return np.abs(np.minimum(self.expr.value, 0)) 131 132 def violation(self): 133 res = self.residual 134 if res is None: 135 raise ValueError("Cannot compute the violation of an constraint " 136 "whose expression is None-valued.") 137 viol = np.linalg.norm(res, ord=2) 138 return viol 139 140 141class Inequality(Constraint): 142 """A constraint of the form :math:`x \\leq y`. 143 144 Parameters 145 ---------- 146 lhs : Expression 147 The expression to be upper-bounded by rhs 148 rhs : Expression 149 The expression to be lower-bounded by lhs 150 constr_id : int 151 A unique id for the constraint. 152 """ 153 def __init__(self, lhs, rhs, constr_id=None) -> None: 154 self._expr = lhs - rhs 155 # TODO remove this restriction. 156 if self._expr.is_complex(): 157 raise ValueError("Inequality constraints cannot be complex.") 158 super(Inequality, self).__init__([lhs, rhs], constr_id) 159 160 def _construct_dual_variables(self, args) -> None: 161 super(Inequality, self)._construct_dual_variables([self._expr]) 162 163 @property 164 def expr(self): 165 return self._expr 166 167 def name(self) -> str: 168 return "%s <= %s" % (self.args[0], self.args[1]) 169 170 @property 171 def shape(self): 172 """int : The shape of the constrained expression.""" 173 return self.expr.shape 174 175 @property 176 def size(self): 177 """int : The size of the constrained expression.""" 178 return self.expr.size 179 180 def is_dcp(self, dpp: bool = False) -> bool: 181 """A non-positive constraint is DCP if its argument is convex.""" 182 if dpp: 183 with scopes.dpp_scope(): 184 return self.expr.is_convex() 185 return self.expr.is_convex() 186 187 def is_dgp(self, dpp: bool = False) -> bool: 188 if dpp: 189 with scopes.dpp_scope(): 190 return (self.args[0].is_log_log_convex() and 191 self.args[1].is_log_log_concave()) 192 return (self.args[0].is_log_log_convex() and 193 self.args[1].is_log_log_concave()) 194 195 def is_dpp(self, context='dcp') -> bool: 196 if context.lower() == 'dcp': 197 return self.is_dcp(dpp=True) 198 elif context.lower() == 'dgp': 199 return self.is_dgp(dpp=True) 200 else: 201 raise ValueError('Unsupported context ', context) 202 203 def is_dqcp(self) -> bool: 204 return ( 205 self.is_dcp() or 206 (self.args[0].is_quasiconvex() and self.args[1].is_constant()) or 207 (self.args[0].is_constant() and self.args[1].is_quasiconcave())) 208 209 @property 210 def residual(self): 211 """The residual of the constraint. 212 213 Returns 214 --------- 215 NumPy.ndarray 216 """ 217 if self.expr.value is None: 218 return None 219 return np.maximum(self.expr.value, 0) 220