Coverage for picos/constraints/uncertain/ucon_conic_aff.py: 100.00%

72 statements  

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

18 

19"""Implements :class:`ConicallyUncertainAffineConstraint`.""" 

20 

21import operator 

22from collections import namedtuple 

23 

24from ... import glyphs 

25from ...apidoc import api_end, api_start 

26from ..constraint import Constraint, ConstraintConversion 

27 

28_API_START = api_start(globals()) 

29# ------------------------------- 

30 

31 

32class ConicallyUncertainAffineConstraint(Constraint): 

33 """A bound on an affine expression with conic uncertainty.""" 

34 

35 class RobustConversion(ConstraintConversion): 

36 """Robust counterpart conversion.""" 

37 

38 @classmethod 

39 def predict(cls, subtype, options): 

40 """Implement :meth:`~.constraint.ConstraintConversion.predict`.""" 

41 from ...expressions import AffineExpression, RealVariable 

42 from .. import AffineConstraint 

43 

44 N = subtype.dim 

45 Z = subtype.universe_subtype 

46 

47 K = Z.cone_type 

48 D = Z.dual_cone_type 

49 

50 K_dim = K.subtype.dim 

51 z_dim = Z.param_dim 

52 

53 y = AffineExpression.make_type( 

54 shape=(K_dim, 1), constant=False, nonneg=False) 

55 

56 yield ("var", RealVariable.make_var_type(dim=K_dim, bnd=0), N) 

57 yield ("con", AffineConstraint.make_type(dim=1, eq=False), N) 

58 yield ("con", AffineConstraint.make_type(z_dim, eq=True), 

59 2*N if subtype.universe_subtype.has_B else N) 

60 yield ("con", y.predict(operator.__lshift__, D), N) 

61 

62 @classmethod 

63 def convert(cls, con, options): 

64 """Implement :meth:`~.constraint.ConstraintConversion.convert`. 

65 

66 Conversion recipe and variable names based on the book 

67 *Robust Optimization* (Ben-Tal, El Ghaoui, Nemirovski, 2009). 

68 """ 

69 from ...expressions import AffineExpression, RealVariable 

70 from ...modeling import Problem 

71 

72 z = con.le0.perturbation 

73 Z = con.le0.universe 

74 P, Q, p, K = Z.A, Z.B, Z.c, Z.K 

75 

76 problem = Problem() 

77 

78 # Handle multidimensional constraints entry-wise. 

79 for i in range(len(con.le0)): 

80 scalar_le0 = con.le0[i] 

81 

82 y = RealVariable("__y#{}".format(i), K.dim) 

83 

84 # The certain linear part. 

85 a0Tx = AffineExpression("a0Tx", (1, 1), { 

86 x: scalar_le0._linear_coefs[x] 

87 for x in scalar_le0._linear_coefs if x is not z}) 

88 

89 # The certain constant part. 

90 b0 = AffineExpression( 

91 "b0", (1, 1), {(): scalar_le0._constant_coef}) 

92 

93 aT = {} 

94 for (x, z), coef in scalar_le0._sorted_bilinear_coefs.items(): 

95 coef = coef[:, :] # Make a copy of the row vector. 

96 coef.size = (z.dim, x.dim) # Devectorize it. 

97 aT[x, z] = coef 

98 

99 # The linear part for each scalar perturbation (v-stacked). 

100 a_Tx = AffineExpression("a_Tx", (z.dim, 1), { 

101 x: aT[x, z] for (x, z) in aT}) 

102 

103 # The constant part for each scalar perturbation (v-stacked). 

104 b_ = AffineExpression("b_", (z.dim, 1), 

105 {(): scalar_le0._linear_coefs[z].T} 

106 if z in scalar_le0._linear_coefs else {}) 

107 

108 problem.add_constraint(p.T*y + a0Tx + b0 <= 0) 

109 problem.add_constraint(P.T*y + a_Tx + b_ == 0) 

110 problem.add_constraint(y << K.dual_cone) 

111 

112 if Q is not None: 

113 problem.add_constraint(Q.T*y == 0) 

114 

115 return problem 

116 

117 def __init__(self, le0): 

118 """Construct an :class:`ConicallyUncertainAffineConstraint`. 

119 

120 :param ~picos.expressions.UncertainAffineExpression le0: 

121 Uncertain expression constrained to be at most zero. 

122 """ 

123 from ...expressions import UncertainAffineExpression 

124 from ...expressions.uncertain.pert_conic import ConicPerturbationSet 

125 

126 assert isinstance(le0, UncertainAffineExpression) 

127 assert isinstance(le0.universe, ConicPerturbationSet) 

128 

129 self.le0 = le0 

130 

131 super(ConicallyUncertainAffineConstraint, self).__init__( 

132 "Conically Uncertain Affine", printSize=True) 

133 

134 Subtype = namedtuple("Subtype", ("dim", "universe_subtype")) 

135 

136 def _subtype(self): 

137 return self.Subtype(len(self.le0), self.le0.universe.subtype) 

138 

139 @classmethod 

140 def _cost(cls, subtype): 

141 return float("inf") 

142 

143 def _expression_names(self): 

144 yield "le0" 

145 

146 def _str(self): 

147 return glyphs.forall( 

148 glyphs.le(self.le0.string, 0), self.le0.perturbation) 

149 

150 def _get_size(self): 

151 return self.le0.shape 

152 

153 def _get_slack(self): 

154 return -self.le0.worst_case_value(direction="max") 

155 

156 

157# -------------------------------------- 

158__all__ = api_end(_API_START, globals())