Coverage for picos/expressions/cone_rsoc.py: 87.72%
57 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) 2019 Maximilian Stahlberg
3# Based on the original picos.expressions module by Guillaume Sagnol.
4#
5# This file is part of PICOS.
6#
7# PICOS is free software: you can redistribute it and/or modify it under the
8# terms of the GNU General Public License as published by the Free Software
9# Foundation, either version 3 of the License, or (at your option) any later
10# version.
11#
12# PICOS is distributed in the hope that it will be useful, but WITHOUT ANY
13# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# ------------------------------------------------------------------------------
20"""Implements :class:`RotatedSecondOrderCone`."""
22import operator
23from collections import namedtuple
25from .. import glyphs
26from ..apidoc import api_end, api_start
27from ..caching import cached_property
28from ..constraints import RSOCConstraint
29from .cone import Cone
30from .exp_affine import AffineExpression
32_API_START = api_start(globals())
33# -------------------------------
36class RotatedSecondOrderCone(Cone):
37 r"""The (narrowed or widened) rotated second order cone.
39 .. _rotatedcone:
41 For :math:`n \in \mathbb{Z}_{\geq 3}` and :math:`p \in \mathbb{R}_{> 0}`,
42 represents the convex cone
44 .. math::
46 \mathcal{R}_{p}^n = \left\{
47 x \in \mathbb{R}^n
48 ~\middle|~
49 p x_1 x_2 \geq \sum_{i = 3}^n x_i^2 \land x_1, x_2 \geq 0
50 \right\}.
52 For :math:`p = 2`, this is the standard rotated second order cone
53 :math:`\mathcal{R}^n` obtained by rotating the
54 :class:`second order cone <picos.SecondOrderCone>` :math:`\mathcal{Q}^n`
55 by :math:`\frac{\pi}{4}` in the :math:`(x_1, x_2)` plane.
57 The default instance of this class has :math:`p = 1`, which can be
58 understood as a narrowed version of the standard cone. This is more
59 convenient for defining the primal problem but it should be noted that
60 :math:`\mathcal{R}_{1}^n` is not self-dual, so working with
61 :math:`p = 2` may seem more natural when the dual problem is of interest.
63 :Dual cone:
65 The dual cone is
67 .. math::
69 \left(\mathcal{R}_{p}^n\right)^* = \left\{
70 x \in \mathbb{R}^n
71 ~\middle|~
72 \frac{4}{p} x_1 x_2 \geq \sum_{i = 2}^n x_i^2 \land x_1, x_2 \geq 0
73 \right\}=\mathcal{R}_{4/p}^n.
75 The cone is thus self-dual for :math:`p = 2`.
76 """
78 def __init__(self, p=1, dim=None):
79 """Construct a rotated second order cone.
81 :param float p:
82 The positive factor :math:`p` in the definition.
83 """
84 try:
85 p = float(p)
86 except Exception as error:
87 raise TypeError("Failed to load the parameter 'p' as a float.") \
88 from error
90 if p <= 0:
91 raise ValueError("The parameter 'p' must be positive.")
93 self._p = p
95 if dim and dim < 3:
96 raise ValueError("The minimal dimension for {} is {}."
97 .format(self.__class__.__name__, 3))
99 typeStr = "Rotated Second Order Cone"
100 if p < 2:
101 typeStr = "Narrowed " + typeStr
102 elif p > 2:
103 typeStr = "Widened " + typeStr
105 symbStr = glyphs.set(glyphs.sep(
106 glyphs.col_vectorize("u", "v", "x"), glyphs.and_(
107 glyphs.le(
108 glyphs.squared(glyphs.norm("x")),
109 glyphs.clever_mul(glyphs.scalar(p), glyphs.mul("u", "v"))),
110 glyphs.ge("u", 0))))
112 Cone.__init__(self, dim, typeStr, symbStr)
114 @property
115 def p(self):
116 """A narrowing (:math:`p < 2`) or widening (:math:`p > 2`) factor."""
117 return self._p
119 def _get_mutables(self):
120 return frozenset()
122 def _replace_mutables(self):
123 return self
125 Subtype = namedtuple("Subtype", ("dim",))
127 def _get_subtype(self):
128 return self.Subtype(self.dim)
130 @classmethod
131 def _predict(cls, subtype, relation, other):
132 assert isinstance(subtype, cls.Subtype)
134 if relation == operator.__rshift__:
135 if issubclass(other.clstype, AffineExpression) \
136 and not subtype.dim or subtype.dim == other.subtype.dim \
137 and other.subtype.dim >= 3:
138 return RSOCConstraint.make_type(other.subtype.dim - 2)
140 return Cone._predict_base(cls, subtype, relation, other)
142 def _rshift_implementation(self, element):
143 if isinstance(element, AffineExpression):
144 self._check_dimension(element)
146 if len(element) < 3:
147 raise TypeError("Elements of the rotated second order cone must"
148 " be at least three-dimensional.")
150 element = element.vec
152 return RSOCConstraint(element[2:], self.p * element[0], element[1])
154 # Handle scenario uncertainty for all cones.
155 return Cone._rshift_base(self, element)
157 @cached_property
158 def dual_cone(self):
159 """Implement :attr:`.cone.Cone.dual_cone`."""
160 return self.__class__(p=(4.0/self._p), dim=self.dim)
163# --------------------------------------
164__all__ = api_end(_API_START, globals())