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

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# ------------------------------------------------------------------------------ 

19 

20"""Implements :class:`RotatedSecondOrderCone`.""" 

21 

22import operator 

23from collections import namedtuple 

24 

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 

31 

32_API_START = api_start(globals()) 

33# ------------------------------- 

34 

35 

36class RotatedSecondOrderCone(Cone): 

37 r"""The (narrowed or widened) rotated second order cone. 

38 

39 .. _rotatedcone: 

40 

41 For :math:`n \in \mathbb{Z}_{\geq 3}` and :math:`p \in \mathbb{R}_{> 0}`, 

42 represents the convex cone 

43 

44 .. math:: 

45 

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\}. 

51 

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. 

56 

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. 

62 

63 :Dual cone: 

64 

65 The dual cone is 

66 

67 .. math:: 

68 

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. 

74 

75 The cone is thus self-dual for :math:`p = 2`. 

76 """ 

77 

78 def __init__(self, p=1, dim=None): 

79 """Construct a rotated second order cone. 

80 

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 

89 

90 if p <= 0: 

91 raise ValueError("The parameter 'p' must be positive.") 

92 

93 self._p = p 

94 

95 if dim and dim < 3: 

96 raise ValueError("The minimal dimension for {} is {}." 

97 .format(self.__class__.__name__, 3)) 

98 

99 typeStr = "Rotated Second Order Cone" 

100 if p < 2: 

101 typeStr = "Narrowed " + typeStr 

102 elif p > 2: 

103 typeStr = "Widened " + typeStr 

104 

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)))) 

111 

112 Cone.__init__(self, dim, typeStr, symbStr) 

113 

114 @property 

115 def p(self): 

116 """A narrowing (:math:`p < 2`) or widening (:math:`p > 2`) factor.""" 

117 return self._p 

118 

119 def _get_mutables(self): 

120 return frozenset() 

121 

122 def _replace_mutables(self): 

123 return self 

124 

125 Subtype = namedtuple("Subtype", ("dim",)) 

126 

127 def _get_subtype(self): 

128 return self.Subtype(self.dim) 

129 

130 @classmethod 

131 def _predict(cls, subtype, relation, other): 

132 assert isinstance(subtype, cls.Subtype) 

133 

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) 

139 

140 return Cone._predict_base(cls, subtype, relation, other) 

141 

142 def _rshift_implementation(self, element): 

143 if isinstance(element, AffineExpression): 

144 self._check_dimension(element) 

145 

146 if len(element) < 3: 

147 raise TypeError("Elements of the rotated second order cone must" 

148 " be at least three-dimensional.") 

149 

150 element = element.vec 

151 

152 return RSOCConstraint(element[2:], self.p * element[0], element[1]) 

153 

154 # Handle scenario uncertainty for all cones. 

155 return Cone._rshift_base(self, element) 

156 

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) 

161 

162 

163# -------------------------------------- 

164__all__ = api_end(_API_START, globals())