Coverage for picos/constraints/uncertain/ucon_conic_aff.py: 100.00%
72 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-26 07:46 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-26 07:46 +0000
1# ------------------------------------------------------------------------------
2# Copyright (C) 2020 Maximilian Stahlberg
3#
4# This file is part of PICOS.
5#
6# PICOS is free software: you can redistribute it and/or modify it under the
7# terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# PICOS is distributed in the hope that it will be useful, but WITHOUT ANY
12# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# this program. If not, see <http://www.gnu.org/licenses/>.
17# ------------------------------------------------------------------------------
19"""Implements :class:`ConicallyUncertainAffineConstraint`."""
21import operator
22from collections import namedtuple
24from ... import glyphs
25from ...apidoc import api_end, api_start
26from ..constraint import Constraint, ConstraintConversion
28_API_START = api_start(globals())
29# -------------------------------
32class ConicallyUncertainAffineConstraint(Constraint):
33 """A bound on an affine expression with conic uncertainty."""
35 class RobustConversion(ConstraintConversion):
36 """Robust counterpart conversion."""
38 @classmethod
39 def predict(cls, subtype, options):
40 """Implement :meth:`~.constraint.ConstraintConversion.predict`."""
41 from ...expressions import AffineExpression, RealVariable
42 from .. import AffineConstraint
44 N = subtype.dim
45 Z = subtype.universe_subtype
47 K = Z.cone_type
48 D = Z.dual_cone_type
50 K_dim = K.subtype.dim
51 z_dim = Z.param_dim
53 y = AffineExpression.make_type(
54 shape=(K_dim, 1), constant=False, nonneg=False)
56 yield ("var", RealVariable.make_var_type(dim=K_dim, bnd=0), N)
57 yield ("con", AffineConstraint.make_type(dim=1, eq=False), N)
58 yield ("con", AffineConstraint.make_type(z_dim, eq=True),
59 2*N if subtype.universe_subtype.has_B else N)
60 yield ("con", y.predict(operator.__lshift__, D), N)
62 @classmethod
63 def convert(cls, con, options):
64 """Implement :meth:`~.constraint.ConstraintConversion.convert`.
66 Conversion recipe and variable names based on the book
67 *Robust Optimization* (Ben-Tal, El Ghaoui, Nemirovski, 2009).
68 """
69 from ...expressions import AffineExpression, RealVariable
70 from ...modeling import Problem
72 z = con.le0.perturbation
73 Z = con.le0.universe
74 P, Q, p, K = Z.A, Z.B, Z.c, Z.K
76 problem = Problem()
78 # Handle multidimensional constraints entry-wise.
79 for i in range(len(con.le0)):
80 scalar_le0 = con.le0[i]
82 y = RealVariable("__y#{}".format(i), K.dim)
84 # The certain linear part.
85 a0Tx = AffineExpression("a0Tx", (1, 1), {
86 x: scalar_le0._linear_coefs[x]
87 for x in scalar_le0._linear_coefs if x is not z})
89 # The certain constant part.
90 b0 = AffineExpression(
91 "b0", (1, 1), {(): scalar_le0._constant_coef})
93 aT = {}
94 for (x, z), coef in scalar_le0._sorted_bilinear_coefs.items():
95 coef = coef[:, :] # Make a copy of the row vector.
96 coef.size = (z.dim, x.dim) # Devectorize it.
97 aT[x, z] = coef
99 # The linear part for each scalar perturbation (v-stacked).
100 a_Tx = AffineExpression("a_Tx", (z.dim, 1), {
101 x: aT[x, z] for (x, z) in aT})
103 # The constant part for each scalar perturbation (v-stacked).
104 b_ = AffineExpression("b_", (z.dim, 1),
105 {(): scalar_le0._linear_coefs[z].T}
106 if z in scalar_le0._linear_coefs else {})
108 problem.add_constraint(p.T*y + a0Tx + b0 <= 0)
109 problem.add_constraint(P.T*y + a_Tx + b_ == 0)
110 problem.add_constraint(y << K.dual_cone)
112 if Q is not None:
113 problem.add_constraint(Q.T*y == 0)
115 return problem
117 def __init__(self, le0):
118 """Construct an :class:`ConicallyUncertainAffineConstraint`.
120 :param ~picos.expressions.UncertainAffineExpression le0:
121 Uncertain expression constrained to be at most zero.
122 """
123 from ...expressions import UncertainAffineExpression
124 from ...expressions.uncertain.pert_conic import ConicPerturbationSet
126 assert isinstance(le0, UncertainAffineExpression)
127 assert isinstance(le0.universe, ConicPerturbationSet)
129 self.le0 = le0
131 super(ConicallyUncertainAffineConstraint, self).__init__(
132 "Conically Uncertain Affine", printSize=True)
134 Subtype = namedtuple("Subtype", ("dim", "universe_subtype"))
136 def _subtype(self):
137 return self.Subtype(len(self.le0), self.le0.universe.subtype)
139 @classmethod
140 def _cost(cls, subtype):
141 return float("inf")
143 def _expression_names(self):
144 yield "le0"
146 def _str(self):
147 return glyphs.forall(
148 glyphs.le(self.le0.string, 0), self.le0.perturbation)
150 def _get_size(self):
151 return self.le0.shape
153 def _get_slack(self):
154 return -self.le0.worst_case_value(direction="max")
157# --------------------------------------
158__all__ = api_end(_API_START, globals())