1""" 2Copyright 2013 Steven Diamond 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 cvxpy.lin_ops.lin_utils as lu 18import cvxpy.utilities as u 19from cvxpy.error import DCPError 20from cvxpy.expressions.expression import Expression 21from cvxpy.interface.matrix_utilities import scalar_value 22from cvxpy.utilities import scopes 23 24 25class Objective(u.Canonical): 26 """An optimization objective. 27 28 Parameters 29 ---------- 30 expr : Expression 31 The expression to act upon. Must be a scalar. 32 33 Raises 34 ------ 35 ValueError 36 If expr is not a scalar. 37 """ 38 39 NAME = "objective" 40 41 def __init__(self, expr) -> None: 42 self.args = [Expression.cast_to_const(expr)] 43 # Validate that the objective resolves to a scalar. 44 if not self.args[0].is_scalar(): 45 raise ValueError("The '%s' objective must resolve to a scalar." 46 % self.NAME) 47 if not self.args[0].is_real(): 48 raise ValueError("The '%s' objective must be real valued." 49 % self.NAME) 50 51 def __repr__(self) -> str: 52 return "%s(%s)" % (self.__class__.__name__, repr(self.args[0])) 53 54 def __str__(self) -> str: 55 return ' '.join([self.NAME, self.args[0].name()]) 56 57 def __radd__(self, other): 58 if other == 0: 59 return self 60 else: 61 raise NotImplementedError() 62 63 def __sub__(self, other): 64 if not isinstance(other, (Minimize, Maximize)): 65 raise NotImplementedError() 66 # Objectives must opposites 67 return self + (-other) 68 69 def __rsub__(self, other): 70 if other == 0: 71 return -self 72 else: 73 raise NotImplementedError() 74 75 def __mul__(self, other): 76 if not isinstance(other, (int, float)): 77 raise NotImplementedError() 78 # If negative, reverse the direction of objective 79 if (type(self) == Maximize) == (other < 0.0): 80 return Minimize(self.args[0] * other) 81 else: 82 return Maximize(self.args[0] * other) 83 84 __rmul__ = __mul__ 85 86 def __div__(self, other): 87 if not isinstance(other, (int, float)): 88 raise NotImplementedError() 89 return self * (1.0/other) 90 91 __truediv__ = __div__ 92 93 @property 94 def value(self): 95 """The value of the objective expression. 96 """ 97 v = self.args[0].value 98 if v is None: 99 return None 100 else: 101 return scalar_value(v) 102 103 def is_quadratic(self) -> bool: 104 """Returns if the objective is a quadratic function. 105 """ 106 return self.args[0].is_quadratic() 107 108 def is_qpwa(self) -> bool: 109 """Returns if the objective is a quadratic of piecewise affine. 110 """ 111 return self.args[0].is_qpwa() 112 113 114class Minimize(Objective): 115 """An optimization objective for minimization. 116 117 Parameters 118 ---------- 119 expr : Expression 120 The expression to minimize. Must be a scalar. 121 122 Raises 123 ------ 124 ValueError 125 If expr is not a scalar. 126 """ 127 128 NAME = "minimize" 129 130 def __neg__(self) -> "Maximize": 131 return Maximize(-self.args[0]) 132 133 def __add__(self, other): 134 if not isinstance(other, (Minimize, Maximize)): 135 raise NotImplementedError() 136 # Objectives must both be Minimize. 137 if type(other) is Minimize: 138 return Minimize(self.args[0] + other.args[0]) 139 else: 140 raise DCPError("Problem does not follow DCP rules.") 141 142 def canonicalize(self): 143 """Pass on the target expression's objective and constraints. 144 """ 145 return self.args[0].canonical_form 146 147 def is_dcp(self, dpp: bool = False) -> bool: 148 """The objective must be convex. 149 """ 150 if dpp: 151 with scopes.dpp_scope(): 152 return self.args[0].is_convex() 153 return self.args[0].is_convex() 154 155 def is_dgp(self, dpp: bool = False) -> bool: 156 """The objective must be log-log convex. 157 """ 158 if dpp: 159 with scopes.dpp_scope(): 160 return self.args[0].is_log_log_convex() 161 return self.args[0].is_log_log_convex() 162 163 def is_dpp(self, context='dcp') -> bool: 164 with scopes.dpp_scope(): 165 if context.lower() == 'dcp': 166 return self.is_dcp(dpp=True) 167 elif context.lower() == 'dgp': 168 return self.is_dgp(dpp=True) 169 else: 170 raise ValueError("Unsupported context ", context) 171 172 def is_dqcp(self) -> bool: 173 """The objective must be quasiconvex. 174 """ 175 return self.args[0].is_quasiconvex() 176 177 @staticmethod 178 def primal_to_result(result): 179 """The value of the objective given the solver primal value. 180 """ 181 return result 182 183 184class Maximize(Objective): 185 """An optimization objective for maximization. 186 187 Parameters 188 ---------- 189 expr : Expression 190 The expression to maximize. Must be a scalar. 191 192 Raises 193 ------ 194 ValueError 195 If expr is not a scalar. 196 """ 197 198 NAME = "maximize" 199 200 def __neg__(self) -> Minimize: 201 return Minimize(-self.args[0]) 202 203 def __add__(self, other): 204 if not isinstance(other, (Minimize, Maximize)): 205 raise NotImplementedError() 206 # Objectives must both be Maximize. 207 if type(other) is Maximize: 208 return Maximize(self.args[0] + other.args[0]) 209 else: 210 raise Exception("Problem does not follow DCP rules.") 211 212 def canonicalize(self): 213 """Negates the target expression's objective. 214 """ 215 obj, constraints = self.args[0].canonical_form 216 return (lu.neg_expr(obj), constraints) 217 218 def is_dcp(self, dpp: bool = False) -> bool: 219 """The objective must be concave. 220 """ 221 if dpp: 222 with scopes.dpp_scope(): 223 return self.args[0].is_concave() 224 return self.args[0].is_concave() 225 226 def is_dgp(self, dpp: bool = False) -> bool: 227 """The objective must be log-log concave. 228 """ 229 if dpp: 230 with scopes.dpp_scope(): 231 return self.args[0].is_log_log_concave() 232 return self.args[0].is_log_log_concave() 233 234 def is_dpp(self, context='dcp') -> bool: 235 with scopes.dpp_scope(): 236 if context.lower() == 'dcp': 237 return self.is_dcp(dpp=True) 238 elif context.lower() == 'dgp': 239 return self.is_dgp(dpp=True) 240 else: 241 raise ValueError("Unsupported context ", context) 242 243 def is_dqcp(self) -> bool: 244 """The objective must be quasiconcave. 245 """ 246 return self.args[0].is_quasiconcave() 247 248 @staticmethod 249 def primal_to_result(result): 250 """The value of the objective given the solver primal value. 251 """ 252 return -result 253