Coverage for picos/constraints/con_prodcone.py: 92.54%
67 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"""Implementation of :class:`ProductConeConstraint`."""
21import operator
22from collections import namedtuple
24from .. import glyphs
25from ..apidoc import api_end, api_start
26from ..caching import cached_property
27from .constraint import ConicConstraint, ConstraintConversion
29_API_START = api_start(globals())
30# -------------------------------
33class ProductConeConstraint(ConicConstraint):
34 """Confines an element inside a real Cartesian product cone."""
36 class Conversion(ConstraintConversion):
37 """Cartesian product cone membership conversion."""
39 @classmethod
40 def predict(cls, subtype, options):
41 """Implement :meth:`~.constraint.ConstraintConversion.predict`."""
42 from ..expressions import AffineExpression
44 for cone_type in subtype.cones:
45 shape = (cone_type.subtype.dim, 1)
47 # HACK: Assume that every relevant slice of the product cone
48 # member is nonconstant.
49 # NOTE: This works as long as the detailed type of any
50 # constraint created via the membership operator << on a
51 # cone depends only on the member's dimensionality.
52 member_type = AffineExpression.make_type(
53 shape=shape, constant=False, nonneg=False)
55 constraint = member_type.predict(operator.__lshift__, cone_type)
57 yield ("con", constraint, 1)
59 @classmethod
60 def convert(cls, con, options):
61 """Implement :meth:`~.constraint.ConstraintConversion.convert`."""
62 # NOTE: Clone problem for extra safety. This is probably not needed.
63 return con._conversion.clone()
65 @classmethod
66 def dual(cls, auxVarPrimals, auxConDuals, options):
67 """Implement :meth:`~.constraint.ConstraintConversion.dual`."""
68 from ..expressions.data import cvxopt_vcat
70 if None in auxConDuals:
71 return None
73 return cvxopt_vcat(auxConDuals)
75 def __init__(self, element, cone):
76 """Construct a :class:`ProductConeConstraint`.
78 :param ~picos.expressions.AffineExpression element:
79 The element confined in the product cone.
80 :param ~picos.expressions.ProductCone cone:
81 The product cone.
82 """
83 from ..expressions import AffineExpression, ProductCone
85 assert isinstance(element, AffineExpression)
86 assert isinstance(cone, ProductCone)
88 self.element = element.vec
89 self.cone = cone
91 super(ProductConeConstraint, self).__init__("Product Cone")
93 @cached_property
94 def conic_membership_form(self):
95 """Implement for :class:`~.constraint.ConicConstraint`."""
96 return self.element, self.cone
98 Subtype = namedtuple("Subtype", ("dim", "cones")) # Same as ProductCone.
100 def _subtype(self):
101 return self.Subtype(*self.cone.subtype)
103 @classmethod
104 def _cost(cls, subtype):
105 return subtype.dim
107 def _expression_names(self):
108 yield "element"
109 yield "cone"
111 def _str(self):
112 return glyphs.element(self.element.string, self.cone.string)
114 def _get_size(self):
115 return (self.cone.dim, 1)
117 def _get_slack(self):
118 return min(con.slack for con in self._conversion.constraints.values())
120 @cached_property
121 def _conversion(self):
122 """Cached version of Conversion.convert as _get_slack also needs it."""
123 from ..expressions import ProductCone
124 from ..modeling import Problem
126 x = self.element
127 C = self.cone
129 P = Problem()
131 offset = 0
132 for Ci in C.cones:
133 assert not isinstance(Ci, ProductCone), \
134 "Product cones are supposed to not contain other product cones."
136 xi = x[offset:offset + Ci.dim]
137 offset += Ci.dim
139 P.add_constraint(xi << Ci)
141 return P
144# --------------------------------------
145__all__ = api_end(_API_START, globals())