Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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:`RandomExtremumAffine`.""" 

20 

21import operator 

22from abc import abstractmethod 

23from collections import namedtuple 

24 

25import cvxopt 

26 

27from ...apidoc import api_end, api_start 

28from ...caching import cached_unary_operator 

29from ...constraints import (Constraint, 

30 MomentAmbiguousExtremumAffineConstraint, 

31 WassersteinAmbiguousExtremumAffineConstraint) 

32from ...formatting import arguments 

33from ..data import convert_operands 

34from ..exp_affine import AffineExpression, Constant 

35from ..exp_extremum import (ExtremumBase, MaximumBase, MaximumConvex, 

36 MinimumBase, MinimumConcave) 

37from ..expression import Expression, refine_operands, validate_prediction 

38from .pert_moment import MomentAmbiguitySet 

39from .pert_wasserstein import WassersteinAmbiguitySet 

40from .uexp_affine import UncertainAffineExpression 

41from .uexpression import UncertainExpression 

42 

43_API_START = api_start(globals()) 

44# ------------------------------- 

45 

46 

47class RandomExtremumAffine(ExtremumBase, UncertainExpression, Expression): 

48 """Base class for random convex or concave piecewise linear expressions. 

49 

50 .. note:: 

51 

52 Unlike other uncertain expression types, this class is limited to 

53 uncertainty of stochastic nature, where using the expression in a 

54 constraint or as an objective function implicitly takes the (worst-case) 

55 expectation of the expression. Non-stochastic uncertainty is handled 

56 within :class:`~picos.expressions.MaximumConvex` and 

57 :class:`~picos.expressions.MinimumConcave` as their behavior, although 

58 designed for certain expression types, already encodes the worst-case 

59 approach of the robust optimization paradigm. 

60 """ 

61 

62 # -------------------------------------------------------------------------- 

63 # Additional abstract methods extending (in spirit) ExtremumBase. 

64 # -------------------------------------------------------------------------- 

65 

66 @property 

67 @abstractmethod 

68 def _certain_class(self): 

69 pass 

70 

71 # -------------------------------------------------------------------------- 

72 # Initialization and factory methods. 

73 # -------------------------------------------------------------------------- 

74 

75 def __init__(self, expressions): 

76 """Construct a :class:`RandomExtremumAffine`. 

77 

78 :param expressions: 

79 A collection of uncertain affine expressions whose uncertainty is of 

80 stochastic nature. 

81 """ 

82 # Load constant data and refine expressions. 

83 expressions = tuple( 

84 x.refined if isinstance(x, Expression) else Constant(x) 

85 for x in expressions) 

86 

87 # Check expression types. 

88 if not all(isinstance(x, (AffineExpression, UncertainAffineExpression)) 

89 for x in expressions): 

90 raise TypeError("{} can only denote the extremum of (uncertain) " 

91 "affine expressions.".format(self.__class__.__name__)) 

92 

93 # Check expression dimension. 

94 if not all(x.scalar for x in expressions): 

95 raise TypeError("{} can only denote the extremum of scalar " 

96 "expressions.".format(self.__class__.__name__)) 

97 

98 perturbations = tuple(set(x.perturbation for x in expressions)) 

99 

100 # Check for a unique perturbation parameter. 

101 if len(perturbations) > 1: 

102 raise ValueError("{} can only denote the extremum of uncertain " 

103 "affine expressions that depend on at most one perturbation " 

104 "parameter, found {}." 

105 .format(self.__class__.__name__, len(perturbations))) 

106 

107 perturbation = perturbations[0] if perturbations else None 

108 universe = perturbation.universe if perturbation else None 

109 

110 # Check for a supported perturbation type. 

111 if not universe.distributional: 

112 raise TypeError("{} can only represent uncertainty parameterized by" 

113 " a distribution or distributional ambiguity set, not {}." 

114 .format(self.__class__.__name__, universe.__class__.__name__)) 

115 

116 typeStr = "{} Uncertain Piecewise Linear Function".format( 

117 self._property_word.title()) 

118 

119 symbStr = self._extremum_glyph( 

120 arguments([x.string for x in expressions])) 

121 

122 Expression.__init__(self, typeStr, symbStr) 

123 

124 self._expressions = expressions 

125 self._perturbation = perturbation 

126 

127 # -------------------------------------------------------------------------- 

128 # Abstract method implementations for ExtremumBase. 

129 # -------------------------------------------------------------------------- 

130 

131 @property 

132 def expressions(self): 

133 """The expressions under the extremum.""" 

134 return self._expressions 

135 

136 # -------------------------------------------------------------------------- 

137 # Method overridings for UncertainExpression. 

138 # -------------------------------------------------------------------------- 

139 

140 @property 

141 def perturbation(self): 

142 """Fast override for :class:`~.uexpression.UncertainExpression`.""" 

143 return self._perturbation 

144 

145 # -------------------------------------------------------------------------- 

146 # Abstract method implementations for Expression, except _predict. 

147 # -------------------------------------------------------------------------- 

148 

149 @cached_unary_operator 

150 def _get_refined(self): 

151 """Implement :meth:`~.expression.Expression._get_refined`.""" 

152 if len(self._expressions) == 1: 

153 return self._expressions[0] 

154 elif all(x.constant for x in self._expressions): 

155 return self._extremum(self._expressions, key=lambda x: x.safe_value) 

156 elif all(x.certain for x in self._expressions): 

157 return self._certain_class(x.refined for x in self._expressions) 

158 else: 

159 return self 

160 

161 Subtype = namedtuple("Subtype", ("argnum", "universe_type")) 

162 

163 def _get_subtype(self): 

164 """Implement :meth:`~.expression.Expression._get_subtype`.""" 

165 return self.Subtype(self.argnum, self.universe.type) 

166 

167 def _get_value(self): 

168 return cvxopt.matrix(self._extremum( 

169 x.safe_value for x in self._expressions)) 

170 

171 # -------------------------------------------------------------------------- 

172 # Constraint-creating operators and _predict. 

173 # -------------------------------------------------------------------------- 

174 

175 @classmethod 

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

177 assert isinstance(subtype, cls.Subtype) 

178 

179 convex = issubclass(cls, RandomMaximumAffine) 

180 concave = issubclass(cls, RandomMinimumAffine) 

181 

182 if relation == operator.__le__: 

183 if not convex: 

184 return NotImplemented 

185 elif relation == operator.__ge__: 

186 if not concave: 

187 return NotImplemented 

188 else: 

189 return NotImplemented 

190 

191 if not issubclass(other.clstype, AffineExpression) \ 

192 or other.subtype.dim != 1: 

193 return NotImplemented 

194 

195 if issubclass(subtype.universe_type.clstype, MomentAmbiguitySet): 

196 return MomentAmbiguousExtremumAffineConstraint.make_type( 

197 extremum_argnum=subtype.argnum, 

198 universe_subtype=subtype.universe_type.subtype) 

199 elif issubclass(subtype.universe_type.clstype, WassersteinAmbiguitySet): 

200 return WassersteinAmbiguousExtremumAffineConstraint.make_type( 

201 extremum_argnum=subtype.argnum, 

202 universe_subtype=subtype.universe_type.subtype) 

203 

204 return NotImplemented 

205 

206 @convert_operands(scalarRHS=True) 

207 @validate_prediction 

208 @refine_operands() 

209 def __le__(self, other): 

210 if not self.convex: 

211 raise TypeError("Cannot upper-bound the nonconvex expression {}." 

212 .format(self.string)) 

213 

214 if not isinstance(other, AffineExpression): 

215 return NotImplemented 

216 

217 if isinstance(self.universe, MomentAmbiguitySet): 

218 return MomentAmbiguousExtremumAffineConstraint( 

219 self, Constraint.LE, other) 

220 elif isinstance(self.universe, WassersteinAmbiguitySet): 

221 return WassersteinAmbiguousExtremumAffineConstraint( 

222 self, Constraint.LE, other) 

223 

224 return NotImplemented 

225 

226 @convert_operands(scalarRHS=True) 

227 @validate_prediction 

228 @refine_operands() 

229 def __ge__(self, other): 

230 if not self.concave: 

231 raise TypeError("Cannot lower-bound the nonconcave expression {}." 

232 .format(self.string)) 

233 

234 if not isinstance(other, AffineExpression): 

235 return NotImplemented 

236 

237 if isinstance(self.universe, MomentAmbiguitySet): 

238 return MomentAmbiguousExtremumAffineConstraint( 

239 self, Constraint.GE, other) 

240 elif isinstance(self.universe, WassersteinAmbiguitySet): 

241 return WassersteinAmbiguousExtremumAffineConstraint( 

242 self, Constraint.GE, other) 

243 

244 return NotImplemented 

245 

246 

247class RandomMaximumAffine(MaximumBase, RandomExtremumAffine): 

248 """The maximum over a set of random affine expressions.""" 

249 

250 # -------------------------------------------------------------------------- 

251 # Abstract method implementations for ExtremumBase. 

252 # -------------------------------------------------------------------------- 

253 

254 @property 

255 def _other_class(self): 

256 return RandomMinimumAffine 

257 

258 # -------------------------------------------------------------------------- 

259 # Abstract method implementations for RandomExtremumAffine. 

260 # -------------------------------------------------------------------------- 

261 

262 @property 

263 def _certain_class(self): 

264 return MaximumConvex 

265 

266 

267class RandomMinimumAffine(MinimumBase, RandomExtremumAffine): 

268 """The minimum over a set of random affine expressions.""" 

269 

270 # -------------------------------------------------------------------------- 

271 # Abstract method implementations for ExtremumBase. 

272 # -------------------------------------------------------------------------- 

273 

274 @property 

275 def _other_class(self): 

276 return RandomMaximumAffine 

277 

278 # -------------------------------------------------------------------------- 

279 # Abstract method implementations for RandomExtremumAffine. 

280 # -------------------------------------------------------------------------- 

281 

282 @property 

283 def _certain_class(self): 

284 return MinimumConcave 

285 

286 

287# -------------------------------------- 

288__all__ = api_end(_API_START, globals())