Coverage for picos/constraints/con_affine.py: 96.72%
122 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-15 14:21 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-15 14:21 +0000
1# ------------------------------------------------------------------------------
2# Copyright (C) 2018-2022 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"""Affine constraint types."""
21from collections import namedtuple
23from .. import glyphs
24from ..apidoc import api_end, api_start
25from ..caching import cached_property
26from .constraint import ConicConstraint, ConstraintConversion
28_API_START = api_start(globals())
29# -------------------------------
32class AffineConstraint(ConicConstraint):
33 """An equality or inequality between two affine expressions."""
35 def __init__(self, lhs, relation, rhs, customString=None):
36 """Construct an :class:`AffineConstraint`.
38 :param ~picos.expressions.AffineExpression lhs:
39 Left hand side expression.
40 :param str relation:
41 Constraint relation symbol.
42 :param ~picos.expressions.AffineExpression rhs:
43 Right hand side expression.
44 :param str customString:
45 Optional string description.
46 """
47 from ..expressions import AffineExpression
49 assert isinstance(lhs, AffineExpression)
50 assert isinstance(rhs, AffineExpression)
51 assert relation in self.LE + self.GE + self.EQ
52 assert lhs.size == rhs.size
54 self.lhs = lhs
55 self.rhs = rhs
56 self.relation = relation
58 super(AffineConstraint, self).__init__(
59 "Affine", customString, printSize=True)
61 @cached_property
62 def conic_membership_form(self):
63 """Implement for :class:`~.constraint.ConicConstraint`."""
64 from ..expressions import NonnegativeOrthant, ZeroSpace
66 element = self.ge0.vec
67 dim = len(element)
69 if self.relation == self.EQ:
70 return element, ZeroSpace(dim=dim)
71 else:
72 return element, NonnegativeOrthant(dim=dim)
74 Subtype = namedtuple("Subtype", ("dim", "eq"))
76 def _subtype(self):
77 return self.Subtype(len(self.lhs), self.relation == self.EQ)
79 @property
80 def smaller(self):
81 """Smaller-or-equal side of the constraint.
83 The smaller-or-equal side expression in case of an inequality, otherwise
84 the left hand side.
85 """
86 return self.rhs if self.relation == self.GE else self.lhs
88 @property
89 def greater(self):
90 """Greater-or-equal side of the constraint.
92 The greater-or-equal side expression in case of an inequality, otherwise
93 the right hand side.
94 """
95 return self.lhs if self.relation == self.GE else self.rhs
97 @cached_property
98 def lmr(self):
99 """Left hand side minus right hand side."""
100 return self.lhs - self.rhs
102 @cached_property
103 def rml(self):
104 """Right hand side minus left hand side."""
105 return self.rhs - self.lhs
107 @property
108 def le0(self):
109 """Expression constrained to be lower than or equal to zero.
111 The expression posed to be less than or equal to zero in case of an
112 inequality, otherwise the left hand side minus the right hand side.
113 """
114 if self.relation == self.GE:
115 return self.rml
116 else:
117 return self.lmr
119 @property
120 def ge0(self):
121 """Expression constrained to be greater than or equal to zero.
123 The expression posed to be greater than or equal to zero in case of an
124 inequality, otherwise the left hand side minus the right hand side.
125 """
126 if self.relation == self.LE:
127 return self.rml
128 else:
129 return self.lmr
131 @classmethod
132 def _cost(cls, subtype):
133 return subtype.dim
135 def _expression_names(self):
136 yield "lhs"
137 yield "rhs"
139 def _str(self):
140 if self.relation == self.LE:
141 return glyphs.le(self.lhs.string, self.rhs.string)
142 elif self.relation == self.GE:
143 return glyphs.ge(self.lhs.string, self.rhs.string)
144 else:
145 return glyphs.eq(self.lhs.string, self.rhs.string)
147 def _get_size(self):
148 return self.lhs.size
150 def _get_slack(self):
151 if self.relation == self.LE:
152 delta = self.rml.safe_value
153 else:
154 delta = self.lmr.safe_value
156 return -abs(delta) if self.relation == self.EQ else delta
158 def bounded_linear_form(self):
159 """Bounded linear form of the constraint.
161 Separates the constraint into a linear function on the left hand side
162 and a constant bound on the right hand side.
164 :returns: A pair ``(linear, bound)`` where ``linear`` is a pure linear
165 expression and ``bound`` is a constant expression.
166 """
167 linear = self.lmr
168 bound = -linear.cst
169 linear = linear + bound
171 return (linear, bound)
174class ComplexAffineConstraint(ConicConstraint):
175 """An equality between affine expressions, at least one being complex."""
177 class RealConversion(ConstraintConversion):
178 """Complex affine equality to real affine equality conversion."""
180 @classmethod
181 def predict(cls, subtype, options):
182 """Implement :meth:`~.constraint.ConstraintConversion.predict`."""
183 yield ("con",
184 AffineConstraint.make_type(dim=2*subtype.dim, eq=True), 1)
186 @classmethod
187 def convert(cls, con, options):
188 """Implement :meth:`~.constraint.ConstraintConversion.convert`."""
189 from ..modeling import Problem
191 P = Problem()
192 P.add_constraint((con.lhs.real // con.lhs.imag)
193 == (con.rhs.real // con.rhs.imag))
195 return P
197 @classmethod
198 def dual(cls, auxVarPrimals, auxConDuals, options):
199 """Implement :meth:`~.constraint.ConstraintConversion.dual`."""
200 assert len(auxConDuals) == 1
202 auxConDual = auxConDuals[0]
203 if auxConDual is None:
204 return None
205 else:
206 n = auxConDual.size[0] // 2
207 return auxConDual[:n, :] + 1j*auxConDual[n:, :]
209 def __init__(self, lhs, rhs, customString=None):
210 """Construct a :class:`ComplexAffineConstraint`.
212 :param ~picos.expressions.AffineExpression lhs:
213 Left hand side expression.
214 :param ~picos.expressions.AffineExpression rhs:
215 Right hand side expression.
216 :param str customString:
217 Optional string description.
218 """
219 from ..expressions import ComplexAffineExpression
221 assert isinstance(lhs, ComplexAffineExpression)
222 assert isinstance(rhs, ComplexAffineExpression)
223 assert lhs.size == rhs.size
225 self.lhs = lhs
226 self.rhs = rhs
228 super(ComplexAffineConstraint, self).__init__(
229 "Complex Equality", customString, printSize=True)
231 @cached_property
232 def conic_membership_form(self):
233 """Implement for :class:`~.constraint.ConicConstraint`."""
234 from ..expressions import ZeroSpace
235 return self.lhs - self.rhs, ZeroSpace()
237 Subtype = namedtuple("Subtype", ("dim",))
239 def _subtype(self):
240 return self.Subtype(len(self.lhs))
242 @classmethod
243 def _cost(cls, subtype):
244 return 2*subtype.dim
246 def _expression_names(self):
247 yield "lhs"
248 yield "rhs"
250 def _str(self):
251 return glyphs.eq(self.lhs.string, self.rhs.string)
253 def _get_size(self):
254 return self.lhs.size
256 def _get_slack(self):
257 return -abs(self.lhs.safe_value - self.rhs.safe_value)
260# --------------------------------------
261__all__ = api_end(_API_START, globals())