Gives the (sub/super)gradient of the expression w.r.t. each variable. Matrix expressions are vectorized, so the gradient is a matrix. None indicates variable values unknown or outside domain. Returns: A map of variable to SciPy CSC sparse matrix or None.
(self)
| 224 | |
| 225 | @property |
| 226 | def grad(self): |
| 227 | """Gives the (sub/super)gradient of the expression w.r.t. each variable. |
| 228 | |
| 229 | Matrix expressions are vectorized, so the gradient is a matrix. |
| 230 | None indicates variable values unknown or outside domain. |
| 231 | |
| 232 | Returns: |
| 233 | A map of variable to SciPy CSC sparse matrix or None. |
| 234 | """ |
| 235 | # Subgrad of g(y) = min f_0(x,y) |
| 236 | # s.t. f_i(x,y) <= 0, i = 1,..,p |
| 237 | # h_i(x,y) == 0, i = 1,...,q |
| 238 | # Given by Df_0(x^*,y) + \sum_i Df_i(x^*,y) \lambda^*_i |
| 239 | # + \sum_i Dh_i(x^*,y) \nu^*_i |
| 240 | # where x^*, \lambda^*_i, \nu^*_i are optimal primal/dual variables. |
| 241 | # Add PSD constraints in same way. |
| 242 | |
| 243 | # Short circuit for constant. |
| 244 | if self.is_constant(): |
| 245 | return u.grad.constant_grad(self) |
| 246 | |
| 247 | old_vals = {var.id: var.value for var in self.variables()} |
| 248 | fix_vars = [] |
| 249 | for var in self.dont_opt_vars: |
| 250 | if var.value is None: |
| 251 | return u.grad.error_grad(self) |
| 252 | else: |
| 253 | fix_vars += [var == var.value] |
| 254 | prob = Problem(self.args[0].objective, |
| 255 | fix_vars + self.args[0].constraints) |
| 256 | prob.solve(solver=self.solver, **self._solve_kwargs) |
| 257 | # Compute gradient. |
| 258 | if prob.status in s.SOLUTION_PRESENT: |
| 259 | sign = self.is_convex() - self.is_concave() |
| 260 | # Form Lagrangian. |
| 261 | lagr = self.args[0].objective.args[0] |
| 262 | for constr in self.args[0].constraints: |
| 263 | # TODO: better way to get constraint expressions. |
| 264 | lagr_multiplier = self.cast_to_const(sign * constr.dual_value) |
| 265 | prod = lagr_multiplier.T @ constr.expr |
| 266 | if prod.is_scalar(): |
| 267 | lagr += sum(prod) |
| 268 | else: |
| 269 | lagr += trace(prod) |
| 270 | grad_map = lagr.grad |
| 271 | result = {var: grad_map[var] for var in self.dont_opt_vars} |
| 272 | else: # Unbounded, infeasible, or solver error. |
| 273 | result = u.grad.error_grad(self) |
| 274 | # Restore the original values to the variables. |
| 275 | for var in self.variables(): |
| 276 | var.value = old_vals[var.id] |
| 277 | return result |
| 278 | |
| 279 | @property |
| 280 | def domain(self): |
nothing calls this directly
no test coverage detected