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

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

18 

19"""Renyi entropy constraints.""" 

20 

21from collections import namedtuple 

22 

23from .. import glyphs 

24from ..apidoc import api_end, api_start 

25from ..caching import cached_property 

26from .constraint import Constraint 

27 

28_API_START = api_start(globals()) 

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

30 

31 

32class BaseRenyiEntrConstraint(Constraint): 

33 """Base class representing general Renyi entropy constraints.""" 

34 

35 def __init__(self, divergence, upperBound): 

36 """Construct a :class:`BaseRenyiEntrConstraint`. 

37 

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

46 

47 assert isinstance(divergence, required_divergence) 

48 assert isinstance(upperBound, AffineExpression) 

49 assert len(upperBound) == 1 

50 assert self._is_valid_alpha(divergence.alpha) 

51 

52 self.divergence = divergence 

53 self.upperBound = upperBound 

54 

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 

59 

60 super(BaseRenyiEntrConstraint, self).__init__(divergence._typeStr) 

61 

62 def _required_type(self): 

63 from ..expressions import AffineExpression 

64 

65 return AffineExpression 

66 

67 @cached_property 

68 def u(self): 

69 r"""The :math:`u` of the divergence, or :math:`1`.""" 

70 from ..expressions import AffineExpression 

71 

72 if self.divergence.u is None: 

73 return AffineExpression.from_constant(1) 

74 else: 

75 return self.divergence.u 

76 

77 @property 

78 def X(self): 

79 """The :math:`X` of the divergence.""" 

80 return self.divergence.X 

81 

82 @property 

83 def Y(self): 

84 """The :math:`Y` of the divergence.""" 

85 return self.divergence.Y 

86 

87 @property 

88 def alpha(self): 

89 r"""The parameter :math:`\alpha`.""" 

90 return self.divergence.alpha 

91 

92 Subtype = namedtuple("Subtype", ("argdim",)) 

93 

94 def _subtype(self): 

95 return self.Subtype(self.X.shape[0] ** 2) 

96 

97 @classmethod 

98 def _cost(cls, subtype): 

99 n = subtype.argdim 

100 return n * (n + 1) + 2 

101 

102 def _expression_names(self): 

103 yield "divergence" 

104 yield "upperBound" 

105 

106 def _str(self): 

107 return glyphs.le(self.divergence.string, self.upperBound.string) 

108 

109 def _get_size(self): 

110 n = self.X.shape[0] 

111 return (2 * n * n + 1, 2) 

112 

113 def _get_slack(self): 

114 return self.upperBound.safe_value - self.divergence.safe_value 

115 

116class RenyiEntrConstraint(BaseRenyiEntrConstraint): 

117 """Upper bound of Renyi entropies. 

118 

119 This is the upper bound on Renyi entropies, represented by  

120 :class:`~picos.expressions.RenyiEntropy`. 

121 """ 

122 

123 def _required_divergence(self): 

124 from ..expressions import RenyiEntropy 

125 

126 return RenyiEntropy 

127 

128 def _is_valid_alpha(self, alpha): 

129 return 0 <= alpha and alpha < 1 

130 

131class ComplexRenyiEntrConstraint(RenyiEntrConstraint): 

132 """Upper bound of complex Renyi entropies.""" 

133 

134 def _required_type(self): 

135 from ..expressions import ComplexAffineExpression 

136 

137 return ComplexAffineExpression 

138 

139class SandRenyiEntrConstraint(BaseRenyiEntrConstraint): 

140 """Upper bound of sandwiched Renyi entropies. 

141 

142 This is the upper bound on sandwiched Renyi entropies, represented by  

143 :class:`~picos.expressions.SandRenyiEntropy`. 

144 """ 

145 

146 def _required_divergence(self): 

147 from ..expressions import SandRenyiEntropy 

148 

149 return SandRenyiEntropy 

150 

151 def _is_valid_alpha(self, alpha): 

152 return 0.5 <= alpha and alpha < 1 

153 

154class ComplexSandRenyiEntrConstraint(SandRenyiEntrConstraint): 

155 """Upper bound of complex sandwiched Renyi entropies.""" 

156 

157 def _required_type(self): 

158 from ..expressions import ComplexAffineExpression 

159 

160 return ComplexAffineExpression 

161 

162 

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

164 

165class BaseQuasiEntrEpiConstraint(Constraint): 

166 """Base class for upper bound on quasi-relative entropies.""" 

167 

168 def __init__(self, divergence, upperBound): 

169 """Construct a :class:`BaseQuasiEntrEpiConstraint`. 

170 

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

179 

180 assert isinstance(divergence, required_divergence) 

181 assert isinstance(upperBound, AffineExpression) 

182 assert len(upperBound) == 1 

183 assert self._is_valid_alpha(divergence.alpha) 

184 

185 self.divergence = divergence 

186 self.upperBound = upperBound 

187 

188 assert isinstance(divergence.X, required_type) 

189 assert isinstance(divergence.Y, required_type) 

190 

191 super(BaseQuasiEntrEpiConstraint, self).__init__(divergence._typeStr) 

192 

193 def _required_type(self): 

194 from ..expressions import AffineExpression 

195 

196 return AffineExpression 

197 

198 @property 

199 def X(self): 

200 """The :math:`X` of the divergence.""" 

201 return self.divergence.X 

202 

203 @cached_property 

204 def Y(self): 

205 """The :math:`Y` of the divergence.""" 

206 return self.divergence.Y 

207 

208 @cached_property 

209 def alpha(self): 

210 r"""The parameter :math:`\alpha`.""" 

211 return self.divergence.alpha 

212 

213 Subtype = namedtuple("Subtype", ("argdim",)) 

214 

215 def _subtype(self): 

216 return self.Subtype(self.X.shape[0] ** 2) 

217 

218 @classmethod 

219 def _cost(cls, subtype): 

220 n = subtype.argdim 

221 return n * (n + 1) + 2 

222 

223 def _expression_names(self): 

224 yield "divergence" 

225 yield "upperBound" 

226 

227 def _str(self): 

228 return glyphs.le(self.divergence.string, self.upperBound.string) 

229 

230 def _get_size(self): 

231 n = self.X.shape[0] 

232 return (2 * n * n + 1, 2) 

233 

234 def _get_slack(self): 

235 return self.upperBound.safe_value - self.divergence.safe_value 

236 

237 

238class QuasiEntrEpiConstraint(BaseQuasiEntrEpiConstraint): 

239 """Upper bound of convex quasi-relative entropies. 

240 

241 This is the upper bound on convex trace functions used to define Renyi 

242 entropies, represented by :class:`~picos.expressions.QuasiEntropy`. 

243 """ 

244 

245 def _required_divergence(self): 

246 from ..expressions import QuasiEntropy 

247 

248 return QuasiEntropy 

249 

250 def _is_valid_alpha(self, alpha): 

251 return (-1 <= alpha and alpha <= 0) or (1 <= alpha and alpha <= 2) 

252 

253 

254class ComplexQuasiEntrEpiConstraint(QuasiEntrEpiConstraint): 

255 """Upper bound of complex convex quasi-relative entropies.""" 

256 

257 def _required_type(self): 

258 from ..expressions import ComplexAffineExpression 

259 

260 return ComplexAffineExpression 

261 

262 

263class SandQuasiEntrEpiConstraint(BaseQuasiEntrEpiConstraint): 

264 """Upper bound of convex sandwiched quasi-relative entropies. 

265 

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

270 

271 def _required_divergence(self): 

272 from ..expressions import SandQuasiEntropy 

273 

274 return SandQuasiEntropy 

275 

276 def _is_valid_alpha(self, alpha): 

277 return 1 <= alpha and alpha <= 2 

278 

279 

280class ComplexSandQuasiEntrEpiConstraint(SandQuasiEntrEpiConstraint): 

281 """Upper bound of complex trace func. used for sand. Renyi entropies.""" 

282 

283 def _required_type(self): 

284 from ..expressions import ComplexAffineExpression 

285 

286 return ComplexAffineExpression 

287 

288# -------------- 

289 

290 

291class BaseQuasiEntrHypoConstraint(Constraint): 

292 """Base class for lower bound on concave quasi-relative entropies.""" 

293 

294 def __init__(self, divergence, lowerBound): 

295 """Construct a :class:`BaseQuasiEntrHypoConstraint`. 

296 

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

305 

306 assert isinstance(divergence, required_divergence) 

307 assert isinstance(lowerBound, AffineExpression) 

308 assert len(lowerBound) == 1 

309 assert self._is_valid_alpha(divergence.alpha) 

310 

311 self.divergence = divergence 

312 self.lowerBound = lowerBound 

313 

314 required_type = self._required_type() 

315 

316 assert isinstance(divergence.X, required_type) 

317 assert isinstance(divergence.Y, required_type) 

318 

319 super(BaseQuasiEntrHypoConstraint, self).__init__(divergence._typeStr) 

320 

321 def _required_type(self): 

322 from ..expressions import AffineExpression 

323 

324 return AffineExpression 

325 

326 @property 

327 def X(self): 

328 """The :math:`X` of the divergence.""" 

329 return self.divergence.X 

330 

331 @cached_property 

332 def Y(self): 

333 """The :math:`Y` of the divergence.""" 

334 return self.divergence.Y 

335 

336 @cached_property 

337 def alpha(self): 

338 r"""The parameter :math:`\alpha`.""" 

339 return self.divergence.alpha 

340 

341 Subtype = namedtuple("Subtype", ("argdim",)) 

342 

343 def _subtype(self): 

344 return self.Subtype(self.X.shape[0] ** 2) 

345 

346 @classmethod 

347 def _cost(cls, subtype): 

348 n = subtype.argdim 

349 return n * (n + 1) + 1 

350 

351 def _expression_names(self): 

352 yield "divergence" 

353 yield "lowerBound" 

354 

355 def _str(self): 

356 return glyphs.ge(self.divergence.string, self.lowerBound.string) 

357 

358 def _get_size(self): 

359 n = self.X.shape[0] 

360 return (2 * n * n + 1, 1) 

361 

362 def _get_slack(self): 

363 return self.lowerBound.safe_value - self.divergence.safe_value 

364 

365class QuasiEntrHypoConstraint(BaseQuasiEntrHypoConstraint): 

366 """Lower bound of concave quasi-relative entropies. 

367 

368 This is the lower bound on concave trace functions used to define Renyi 

369 entropies, represented by :class:`~picos.expressions.QuasiEntropy`. 

370 """ 

371 

372 def _required_divergence(self): 

373 from ..expressions import QuasiEntropy 

374 

375 return QuasiEntropy 

376 

377 def _is_valid_alpha(self, alpha): 

378 return 0 <= alpha and alpha <= 1 

379 

380 

381class ComplexQuasiEntrHypoConstraint(QuasiEntrHypoConstraint): 

382 """Lower bound of complex concave quasi-relative entropies.""" 

383 

384 def _required_type(self): 

385 from ..expressions import ComplexAffineExpression 

386 

387 return ComplexAffineExpression 

388 

389 

390class SandQuasiEntrHypoConstraint(BaseQuasiEntrHypoConstraint): 

391 """Lower bound of concave sandwiched quasi-relative entropies. 

392 

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

397 

398 def _required_divergence(self): 

399 from ..expressions import SandQuasiEntropy 

400 

401 return SandQuasiEntropy 

402 

403 def _is_valid_alpha(self, alpha): 

404 return 0.5 <= alpha and alpha <= 1 

405 

406 

407class ComplexSandQuasiEntrHypoConstraint(SandQuasiEntrHypoConstraint): 

408 """Lower bound of complex concave sandwiched quasi-relative entropies.""" 

409 

410 def _required_type(self): 

411 from ..expressions import ComplexAffineExpression 

412 

413 return ComplexAffineExpression 

414 

415# -------------------------------------- 

416__all__ = api_end(_API_START, globals())