Coverage for picos/constraints/con_renyientr.py: 93.63%
204 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-12 07:53 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-12 07:53 +0000
1# ------------------------------------------------------------------------------
2# Copyright (C) 2025 Kerry He
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"""Renyi entropy constraints."""
21from collections import namedtuple
23from .. import glyphs
24from ..apidoc import api_end, api_start
25from ..caching import cached_property
26from .constraint import Constraint
28_API_START = api_start(globals())
29# -------------------------------
32class BaseRenyiEntrConstraint(Constraint):
33 """Base class representing general Renyi entropy constraints."""
35 def __init__(self, divergence, upperBound):
36 """Construct a :class:`BaseRenyiEntrConstraint`.
38 :param ~picos.expressions.QuasiEntropy divergence:
39 Constrained expression.
40 :param ~picos.expressions.AffineExpression upperBound:
41 Upper bound on the expression.
42 """
43 from ..expressions import AffineExpression
44 required_divergence = self._required_divergence()
45 required_type = self._required_type()
47 assert isinstance(divergence, required_divergence)
48 assert isinstance(upperBound, AffineExpression)
49 assert len(upperBound) == 1
50 assert self._is_valid_alpha(divergence.alpha)
52 self.divergence = divergence
53 self.upperBound = upperBound
55 assert isinstance(divergence.X, required_type)
56 assert isinstance(divergence.Y, required_type)
57 assert isinstance(divergence.u, AffineExpression) \
58 or divergence.u is None
60 super(BaseRenyiEntrConstraint, self).__init__(divergence._typeStr)
62 def _required_type(self):
63 from ..expressions import AffineExpression
65 return AffineExpression
67 @cached_property
68 def u(self):
69 r"""The :math:`u` of the divergence, or :math:`1`."""
70 from ..expressions import AffineExpression
72 if self.divergence.u is None:
73 return AffineExpression.from_constant(1)
74 else:
75 return self.divergence.u
77 @property
78 def X(self):
79 """The :math:`X` of the divergence."""
80 return self.divergence.X
82 @property
83 def Y(self):
84 """The :math:`Y` of the divergence."""
85 return self.divergence.Y
87 @property
88 def alpha(self):
89 r"""The parameter :math:`\alpha`."""
90 return self.divergence.alpha
92 Subtype = namedtuple("Subtype", ("argdim",))
94 def _subtype(self):
95 return self.Subtype(self.X.shape[0] ** 2)
97 @classmethod
98 def _cost(cls, subtype):
99 n = subtype.argdim
100 return n * (n + 1) + 2
102 def _expression_names(self):
103 yield "divergence"
104 yield "upperBound"
106 def _str(self):
107 return glyphs.le(self.divergence.string, self.upperBound.string)
109 def _get_size(self):
110 n = self.X.shape[0]
111 return (2 * n * n + 1, 2)
113 def _get_slack(self):
114 return self.upperBound.safe_value - self.divergence.safe_value
116class RenyiEntrConstraint(BaseRenyiEntrConstraint):
117 """Upper bound of Renyi entropies.
119 This is the upper bound on Renyi entropies, represented by
120 :class:`~picos.expressions.RenyiEntropy`.
121 """
123 def _required_divergence(self):
124 from ..expressions import RenyiEntropy
126 return RenyiEntropy
128 def _is_valid_alpha(self, alpha):
129 return 0 <= alpha and alpha < 1
131class ComplexRenyiEntrConstraint(RenyiEntrConstraint):
132 """Upper bound of complex Renyi entropies."""
134 def _required_type(self):
135 from ..expressions import ComplexAffineExpression
137 return ComplexAffineExpression
139class SandRenyiEntrConstraint(BaseRenyiEntrConstraint):
140 """Upper bound of sandwiched Renyi entropies.
142 This is the upper bound on sandwiched Renyi entropies, represented by
143 :class:`~picos.expressions.SandRenyiEntropy`.
144 """
146 def _required_divergence(self):
147 from ..expressions import SandRenyiEntropy
149 return SandRenyiEntropy
151 def _is_valid_alpha(self, alpha):
152 return 0.5 <= alpha and alpha < 1
154class ComplexSandRenyiEntrConstraint(SandRenyiEntrConstraint):
155 """Upper bound of complex sandwiched Renyi entropies."""
157 def _required_type(self):
158 from ..expressions import ComplexAffineExpression
160 return ComplexAffineExpression
163# ----------------
165class BaseQuasiEntrEpiConstraint(Constraint):
166 """Base class for upper bound on quasi-relative entropies."""
168 def __init__(self, divergence, upperBound):
169 """Construct a :class:`BaseQuasiEntrEpiConstraint`.
171 :param ~picos.expressions.QuasiEntropy divergence:
172 Constrained expression.
173 :param ~picos.expressions.AffineExpression upperBound:
174 Upper bound on the expression.
175 """
176 from ..expressions import AffineExpression
177 required_divergence = self._required_divergence()
178 required_type = self._required_type()
180 assert isinstance(divergence, required_divergence)
181 assert isinstance(upperBound, AffineExpression)
182 assert len(upperBound) == 1
183 assert self._is_valid_alpha(divergence.alpha)
185 self.divergence = divergence
186 self.upperBound = upperBound
188 assert isinstance(divergence.X, required_type)
189 assert isinstance(divergence.Y, required_type)
191 super(BaseQuasiEntrEpiConstraint, self).__init__(divergence._typeStr)
193 def _required_type(self):
194 from ..expressions import AffineExpression
196 return AffineExpression
198 @property
199 def X(self):
200 """The :math:`X` of the divergence."""
201 return self.divergence.X
203 @cached_property
204 def Y(self):
205 """The :math:`Y` of the divergence."""
206 return self.divergence.Y
208 @cached_property
209 def alpha(self):
210 r"""The parameter :math:`\alpha`."""
211 return self.divergence.alpha
213 Subtype = namedtuple("Subtype", ("argdim",))
215 def _subtype(self):
216 return self.Subtype(self.X.shape[0] ** 2)
218 @classmethod
219 def _cost(cls, subtype):
220 n = subtype.argdim
221 return n * (n + 1) + 2
223 def _expression_names(self):
224 yield "divergence"
225 yield "upperBound"
227 def _str(self):
228 return glyphs.le(self.divergence.string, self.upperBound.string)
230 def _get_size(self):
231 n = self.X.shape[0]
232 return (2 * n * n + 1, 2)
234 def _get_slack(self):
235 return self.upperBound.safe_value - self.divergence.safe_value
238class QuasiEntrEpiConstraint(BaseQuasiEntrEpiConstraint):
239 """Upper bound of convex quasi-relative entropies.
241 This is the upper bound on convex trace functions used to define Renyi
242 entropies, represented by :class:`~picos.expressions.QuasiEntropy`.
243 """
245 def _required_divergence(self):
246 from ..expressions import QuasiEntropy
248 return QuasiEntropy
250 def _is_valid_alpha(self, alpha):
251 return (-1 <= alpha and alpha <= 0) or (1 <= alpha and alpha <= 2)
254class ComplexQuasiEntrEpiConstraint(QuasiEntrEpiConstraint):
255 """Upper bound of complex convex quasi-relative entropies."""
257 def _required_type(self):
258 from ..expressions import ComplexAffineExpression
260 return ComplexAffineExpression
263class SandQuasiEntrEpiConstraint(BaseQuasiEntrEpiConstraint):
264 """Upper bound of convex sandwiched quasi-relative entropies.
266 This is the upper bound on convex trace functions used to define sandwiched
267 Renyi entropies, represented by
268 :class:`~picos.expressions.SandQuasiEntropy`.
269 """
271 def _required_divergence(self):
272 from ..expressions import SandQuasiEntropy
274 return SandQuasiEntropy
276 def _is_valid_alpha(self, alpha):
277 return 1 <= alpha and alpha <= 2
280class ComplexSandQuasiEntrEpiConstraint(SandQuasiEntrEpiConstraint):
281 """Upper bound of complex trace func. used for sand. Renyi entropies."""
283 def _required_type(self):
284 from ..expressions import ComplexAffineExpression
286 return ComplexAffineExpression
288# --------------
291class BaseQuasiEntrHypoConstraint(Constraint):
292 """Base class for lower bound on concave quasi-relative entropies."""
294 def __init__(self, divergence, lowerBound):
295 """Construct a :class:`BaseQuasiEntrHypoConstraint`.
297 :param ~picos.expressions.QuasiEntropy divergence:
298 Constrained expression.
299 :param ~picos.expressions.AffineExpression lowerBound:
300 Lower bound on the expression.
301 """
302 from ..expressions import AffineExpression
303 required_divergence = self._required_divergence()
304 required_type = self._required_type()
306 assert isinstance(divergence, required_divergence)
307 assert isinstance(lowerBound, AffineExpression)
308 assert len(lowerBound) == 1
309 assert self._is_valid_alpha(divergence.alpha)
311 self.divergence = divergence
312 self.lowerBound = lowerBound
314 required_type = self._required_type()
316 assert isinstance(divergence.X, required_type)
317 assert isinstance(divergence.Y, required_type)
319 super(BaseQuasiEntrHypoConstraint, self).__init__(divergence._typeStr)
321 def _required_type(self):
322 from ..expressions import AffineExpression
324 return AffineExpression
326 @property
327 def X(self):
328 """The :math:`X` of the divergence."""
329 return self.divergence.X
331 @cached_property
332 def Y(self):
333 """The :math:`Y` of the divergence."""
334 return self.divergence.Y
336 @cached_property
337 def alpha(self):
338 r"""The parameter :math:`\alpha`."""
339 return self.divergence.alpha
341 Subtype = namedtuple("Subtype", ("argdim",))
343 def _subtype(self):
344 return self.Subtype(self.X.shape[0] ** 2)
346 @classmethod
347 def _cost(cls, subtype):
348 n = subtype.argdim
349 return n * (n + 1) + 1
351 def _expression_names(self):
352 yield "divergence"
353 yield "lowerBound"
355 def _str(self):
356 return glyphs.ge(self.divergence.string, self.lowerBound.string)
358 def _get_size(self):
359 n = self.X.shape[0]
360 return (2 * n * n + 1, 1)
362 def _get_slack(self):
363 return self.lowerBound.safe_value - self.divergence.safe_value
365class QuasiEntrHypoConstraint(BaseQuasiEntrHypoConstraint):
366 """Lower bound of concave quasi-relative entropies.
368 This is the lower bound on concave trace functions used to define Renyi
369 entropies, represented by :class:`~picos.expressions.QuasiEntropy`.
370 """
372 def _required_divergence(self):
373 from ..expressions import QuasiEntropy
375 return QuasiEntropy
377 def _is_valid_alpha(self, alpha):
378 return 0 <= alpha and alpha <= 1
381class ComplexQuasiEntrHypoConstraint(QuasiEntrHypoConstraint):
382 """Lower bound of complex concave quasi-relative entropies."""
384 def _required_type(self):
385 from ..expressions import ComplexAffineExpression
387 return ComplexAffineExpression
390class SandQuasiEntrHypoConstraint(BaseQuasiEntrHypoConstraint):
391 """Lower bound of concave sandwiched quasi-relative entropies.
393 This is the lower bound on concave trace functions used to define sandwiched
394 Renyi entropies, represented by
395 :class:`~picos.expressions.SandQuasiEntropy`.
396 """
398 def _required_divergence(self):
399 from ..expressions import SandQuasiEntropy
401 return SandQuasiEntropy
403 def _is_valid_alpha(self, alpha):
404 return 0.5 <= alpha and alpha <= 1
407class ComplexSandQuasiEntrHypoConstraint(SandQuasiEntrHypoConstraint):
408 """Lower bound of complex concave sandwiched quasi-relative entropies."""
410 def _required_type(self):
411 from ..expressions import ComplexAffineExpression
413 return ComplexAffineExpression
415# --------------------------------------
416__all__ = api_end(_API_START, globals())