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) 2019-2020 Maximilian Stahlberg 

3# Based in parts on the tools 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 functions that create or modify algebraic expressions.""" 

21 

22import builtins 

23import functools 

24import itertools 

25 

26import cvxopt 

27import numpy 

28 

29from .. import glyphs 

30from ..apidoc import api_end, api_start 

31from ..formatting import arguments 

32from ..legacy import deprecated, throw_deprecation_warning 

33from .cone_expcone import ExponentialCone 

34from .cone_rsoc import RotatedSecondOrderCone 

35from .cone_soc import SecondOrderCone 

36from .data import convert_and_refine_arguments, cvxopt_vcat 

37from .exp_affine import AffineExpression, BiaffineExpression, Constant 

38from .exp_detrootn import DetRootN 

39from .exp_entropy import NegativeEntropy 

40from .exp_extremum import MaximumConvex, MinimumConcave 

41from .exp_geomean import GeometricMean 

42from .exp_logsumexp import LogSumExp 

43from .exp_norm import Norm 

44from .exp_powtrace import PowerTrace 

45from .exp_sumexp import SumExponentials 

46from .exp_sumxtr import SumExtremes 

47from .expression import Expression 

48from .set_ball import Ball 

49from .set_simplex import Simplex 

50from .uncertain.uexp_affine import UncertainAffineExpression 

51from .uncertain.uexp_rand_pwl import RandomMaximumAffine, RandomMinimumAffine 

52 

53_API_START = api_start(globals()) 

54# ------------------------------- 

55 

56 

57# ------------------------------------------------------------------------------ 

58# Algebraic functions with a logic of their own. 

59# ------------------------------------------------------------------------------ 

60 

61 

62def sum(lst, it=None, indices=None): 

63 """Sum PICOS expressions and give the result a meaningful description. 

64 

65 This is a replacement for Python's :func:`sum` that produces sensible string 

66 representations when summing PICOS expressions. 

67 

68 :param lst: A list of 

69 :class:`~picos.expressions.ComplexAffineExpression`, or a 

70 single affine expression whose elements shall be summed. 

71 :type lst: list or tuple or 

72 ~picos.expressions.ComplexAffineExpression 

73 

74 :param it: DEPRECATED 

75 :param indices: DEPRECATED 

76 

77 :Example: 

78 

79 >>> import builtins 

80 >>> import picos 

81 >>> x = picos.RealVariable("x", 5) 

82 >>> e = [x[i]*x[i+1] for i in range(len(x) - 1)] 

83 >>> builtins.sum(e) 

84 <Quadratic Expression: x[0]·x[1] + x[1]·x[2] + x[2]·x[3] + x[3]·x[4]> 

85 >>> picos.sum(e) 

86 <Quadratic Expression: ∑(x[i]·x[i+1] : i ∈ [0…3])> 

87 >>> picos.sum(x) # The same as (x|1). 

88 <1×1 Real Linear Expression: ∑(x)> 

89 """ 

90 if it is not None or indices is not None: 

91 # Deprecated as of 2.0. 

92 throw_deprecation_warning("Arguments 'it' and 'indices' to picos.sum " 

93 "are deprecated and ignored.") 

94 

95 if isinstance(lst, Expression): 

96 if isinstance(lst, BiaffineExpression): 

97 return (lst | 1.0) 

98 else: 

99 raise TypeError( 

100 "PICOS doesn't know how to sum over a single {}." 

101 .format(type(lst).__name__)) 

102 

103 # Resort to Python's built-in sum when no summand is a PICOS expression. 

104 if not any(isinstance(expression, Expression) for expression in lst): 

105 return builtins.sum(lst) 

106 

107 # If at least one summand is a PICOS expression, attempt to convert others. 

108 try: 

109 lst = [Constant(x) if not isinstance(x, Expression) else x for x in lst] 

110 except Exception as error: 

111 raise TypeError("Failed to convert some non-expression argument to a " 

112 "PICOS constant.") from error 

113 

114 if len(lst) == 0: 

115 return Constant(0) 

116 elif len(lst) == 1: 

117 return lst[0] 

118 elif len(lst) == 2: 

119 return lst[0] + lst[1] 

120 

121 theSum = lst[0] 

122 for expression in lst[1:]: 

123 theSum += expression 

124 

125 theSum._symbStr = glyphs.sum(arguments([exp.string for exp in lst])) 

126 

127 return theSum 

128 

129 

130def block(nested, shapes=None, name=None): 

131 """Create an affine block matrix expression. 

132 

133 Given a two-level nested iterable container (e.g. a list of lists) of PICOS 

134 affine expressions or constant data values or a mix thereof, this creates an 

135 affine block matrix where each inner container represents one block row and 

136 each expression or constant represents one block. 

137 

138 Blocks that are given as PICOS expressions are never reshaped or 

139 broadcasted. Their shapes must already be consistent. Blocks that are given 

140 as constant data values are reshaped or broadcasted as necessary **to match 

141 existing PICOS expressions**. This means you can specify blocks as e.g. 

142 ``"I"`` or ``0`` and PICOS will load them as matrices with the smallest 

143 shape that is consistent with other blocks given as PICOS expressions. 

144 

145 Since constant data values are not reshaped or broadcasted with respect to 

146 each other, the ``shapes`` parameter allows a manual clarification of block 

147 shapes. It must be consistent with the shapes of blocks given as PICOS 

148 expressions (they are still not reshaped or broadcasted). 

149 

150 :param nested: 

151 The blocks. 

152 :type nested: 

153 tuple(tuple) or list(list) 

154 

155 :param shapes: 

156 A pair ``(rows, columns)`` where ``rows`` defines the number of rows for 

157 each block row and ``columns`` defines the number of columns for each 

158 block column. You can put a ``0`` or :obj:`None` as a wildcard. 

159 :type shapes: 

160 tuple(tuple) or list(list) 

161 

162 :param str name: 

163 Name or string description of the resulting block matrix. If 

164 :obj:`None`, a descriptive string will be generated. 

165 

166 :Example: 

167 

168 >>> from picos import block, Constant, RealVariable 

169 >>> C = Constant("C", range(6), (3, 2)) 

170 >>> d = Constant("d", 0.5, 2) 

171 >>> x = RealVariable("x", 3) 

172 >>> A = block([[ C, x ], 

173 ... ["I", d ]]); A 

174 <5×3 Real Affine Expression: [C, x; I, d]> 

175 >>> x.value = [60, 70, 80] 

176 >>> print(A) 

177 [ 0.00e+00 3.00e+00 6.00e+01] 

178 [ 1.00e+00 4.00e+00 7.00e+01] 

179 [ 2.00e+00 5.00e+00 8.00e+01] 

180 [ 1.00e+00 0.00e+00 5.00e-01] 

181 [ 0.00e+00 1.00e+00 5.00e-01] 

182 >>> B = block([[ C, x ], # With a shape hint. 

183 ... ["I", 0 ]], shapes=((3, 2), (2, 1))); B 

184 <5×3 Real Affine Expression: [C, x; I, 0]> 

185 >>> print(B) 

186 [ 0.00e+00 3.00e+00 6.00e+01] 

187 [ 1.00e+00 4.00e+00 7.00e+01] 

188 [ 2.00e+00 5.00e+00 8.00e+01] 

189 [ 1.00e+00 0.00e+00 0.00e+00] 

190 [ 0.00e+00 1.00e+00 0.00e+00] 

191 """ 

192 # In a first stage, determine and validate fixed shapes from PICOS 

193 # expressions, then load the remaining data to be consistent with the fixed 

194 # shapes. In a second stage, validate also the shapes of the loaded data. 

195 for stage in range(2): 

196 R = numpy.array([[ # The row count for each block. 

197 x.shape[0] if isinstance(x, BiaffineExpression) else 0 for x in row] 

198 for row in nested], dtype=int) 

199 

200 M = [] # The row count for each block row. 

201 for i, Ri in enumerate(R): 

202 m = set(int(x) for x in Ri[numpy.nonzero(Ri)]) 

203 

204 if shapes and shapes[0][i]: 

205 m.add(shapes[0][i]) 

206 

207 if len(m) > 1: 

208 raise TypeError( 

209 "Inconsistent number of rows in block row {}: {}." 

210 .format(i + 1, m)) 

211 elif len(m) == 1: 

212 M.append(int(m.pop())) 

213 else: 

214 assert stage == 0, "All blocks should have a shape by now." 

215 M.append(None) 

216 

217 C = numpy.array([[ # The column count for each block. 

218 x.shape[1] if isinstance(x, BiaffineExpression) else 0 for x in row] 

219 for row in nested], dtype=int) 

220 

221 N = [] # The column count for each block column. 

222 for j, Cj in enumerate(C.T): 

223 n = set(int(x) for x in Cj[numpy.nonzero(Cj)]) 

224 

225 if shapes and shapes[1][j]: 

226 n.add(shapes[1][j]) 

227 

228 if len(n) > 1: 

229 raise TypeError( 

230 "Inconsistent number of columns in block column {}: {}." 

231 .format(j + 1, n)) 

232 elif len(n) == 1: 

233 N.append(n.pop()) 

234 else: 

235 assert stage == 0, "All blocks should have a shape by now." 

236 N.append(None) 

237 

238 if stage == 0: 

239 nested = [[ 

240 x if isinstance(x, BiaffineExpression) 

241 else Constant(x, shape=(M[i], N[j])) 

242 for j, x in enumerate(row)] for i, row in enumerate(nested)] 

243 

244 # List the blocks in block-row-major order. 

245 blocks = [block for blockRow in nested for block in blockRow] 

246 

247 # Find the common base type of all expressions. 

248 basetype = functools.reduce(BiaffineExpression._common_basetype.__func__, 

249 (block.__class__ for block in blocks), AffineExpression) 

250 typecode = basetype._get_typecode() 

251 

252 # Allow constant time random access to the block dimensions. 

253 M, N = tuple(M), tuple(N) 

254 

255 # Compute the row (column) offsets for each block row (block column). 

256 MOffsets = tuple(int(x) for x in numpy.cumsum((0,) + M)) 

257 NOffsets = tuple(int(x) for x in numpy.cumsum((0,) + N)) 

258 

259 # Compute the full matrix dimensions. 

260 m = builtins.sum(M) 

261 n = builtins.sum(N) 

262 mn = m*n 

263 

264 # Compute row and column offsets for each block in block-row-major-order. 

265 MLen = len(N) 

266 blockIndices = tuple(divmod(k, MLen) for k in range(len(blocks))) 

267 blockOffsets = tuple( 

268 (MOffsets[blockIndices[k][0]], NOffsets[blockIndices[k][1]]) 

269 for k in range(len(blocks))) 

270 

271 # Helper function to compute the matrix T (see below). 

272 def _I(): 

273 for k, block in enumerate(blocks): 

274 rows, cols = block.shape 

275 i, j = blockOffsets[k] 

276 for c in range(cols): 

277 columnOffset = (j + c)*m + i 

278 yield range(columnOffset, columnOffset + rows) 

279 

280 # Compute a sparse linear operator matrix T that transforms the stacked 

281 # column-major vectorizations of the blocks in block-row-major order to the 

282 # column-major vectorization of the full matrix. 

283 V = tuple(itertools.repeat(1, mn)) 

284 I = tuple(itertools.chain(*_I())) 

285 J = range(mn) 

286 T = cvxopt.spmatrix(V, I, J, (mn, mn), typecode) 

287 

288 # Obtain all coefficient keys. 

289 keys = set(key for block in blocks for key in block._coefs.keys()) 

290 

291 # Stack all coefficient matrices in block-row-major order and apply T. 

292 coefs = {} 

293 for mtbs in keys: 

294 dim = functools.reduce(lambda x, y: 

295 (x if isinstance(x, int) else x.dim)*y.dim, mtbs, 1) 

296 

297 coefs[mtbs] = T*cvxopt_vcat([ 

298 block._coefs[mtbs] if mtbs in block._coefs 

299 else cvxopt.spmatrix([], [], [], (len(block), dim), typecode) 

300 for block in blocks]) 

301 

302 # Build the string description. 

303 if name: 

304 string = str(name) 

305 elif len(blocks) > 9: 

306 string = glyphs.matrix(glyphs.shape((m, n))) 

307 else: 

308 string = functools.reduce( 

309 lambda x, y: glyphs.matrix_cat(x, y, False), 

310 (functools.reduce( 

311 lambda x, y: glyphs.matrix_cat(x, y, True), 

312 (block.string for block in blockRow)) 

313 for blockRow in nested)) 

314 

315 return basetype(string, (m, n), coefs) 

316 

317 

318def max(lst): 

319 """Denote the maximum over a collection of convex scalar expressions. 

320 

321 If instead of a collection of expressions only a single multidimensional 

322 affine expression is given, this denotes its largest element instead. 

323 

324 If some individual expressions are uncertain and their uncertainty is not of 

325 stochastic but of worst-case nature (robust optimization), then the maximum 

326 implicitly goes over their perturbation parameters as well. 

327 

328 :param lst: 

329 A list of convex expressions or a single affine expression. 

330 :type lst: 

331 list or tuple or ~picos.expressions.AffineExpression 

332 

333 :Example: 

334 

335 >>> from picos import RealVariable, max, sum 

336 >>> x = RealVariable("x", 5) 

337 >>> max(x) 

338 <Largest Element: max(x)> 

339 >>> max(x) <= 2 # The same as x <= 2. 

340 <Largest Element Constraint: max(x) ≤ 2> 

341 >>> max([sum(x), abs(x)]) 

342 <Maximum of Convex Functions: max(∑(x), ‖x‖)> 

343 >>> max([sum(x), abs(x)]) <= 2 # Both must be <= 2. 

344 <Maximum of Convex Functions Constraint: max(∑(x), ‖x‖) ≤ 2> 

345 >>> from picos.uncertain import UnitBallPerturbationSet 

346 >>> z = UnitBallPerturbationSet("z", 5).parameter 

347 >>> max([sum(x), x.T*z]) # Also maximize over z. 

348 <Maximum of Convex Functions: max(∑(x), max_z xᵀ·z)> 

349 """ 

350 UAE = UncertainAffineExpression 

351 

352 if isinstance(lst, AffineExpression): 

353 return SumExtremes(lst, 1, largest=True, eigenvalues=False) 

354 elif isinstance(lst, Expression): 

355 raise TypeError("May only denote the maximum of a single affine " 

356 "expression or of multiple (convex) expressions.") 

357 

358 try: 

359 lst = [Constant(x) if not isinstance(x, Expression) else x for x in lst] 

360 except Exception as error: 

361 raise TypeError("Failed to convert some non-expression argument to a " 

362 "PICOS constant.") from error 

363 

364 if any(isinstance(x, UAE) for x in lst) \ 

365 and all(isinstance(x, (AffineExpression, UAE)) for x in lst) \ 

366 and all(x.certain or x.universe.distributional for x in lst): 

367 return RandomMaximumAffine(lst) 

368 else: 

369 return MaximumConvex(lst) 

370 

371 

372def min(lst): 

373 """Denote the minimum over a collection of concave scalar expressions. 

374 

375 If instead of a collection of expressions only a single multidimensional 

376 affine expression is given, this denotes its smallest element instead. 

377 

378 If some individual expressions are uncertain and their uncertainty is not of 

379 stochastic but of worst-case nature (robust optimization), then the minimum 

380 implicitly goes over their perturbation parameters as well. 

381 

382 :param lst: 

383 A list of concave expressions or a single affine expression. 

384 :type lst: 

385 list or tuple or ~picos.expressions.AffineExpression 

386 

387 :Example: 

388 

389 >>> from picos import RealVariable, min, sum 

390 >>> x = RealVariable("x", 5) 

391 >>> min(x) 

392 <Smallest Element: min(x)> 

393 >>> min(x) >= 2 # The same as x >= 2. 

394 <Smallest Element Constraint: min(x) ≥ 2> 

395 >>> min([sum(x), -x[0]**2]) 

396 <Minimum of Concave Functions: min(∑(x), -x[0]²)> 

397 >>> min([sum(x), -x[0]**2]) >= 2 # Both must be >= 2. 

398 <Minimum of Concave Functions Constraint: min(∑(x), -x[0]²) ≥ 2> 

399 >>> from picos.uncertain import UnitBallPerturbationSet 

400 >>> z = UnitBallPerturbationSet("z", 5).parameter 

401 >>> min([sum(x), x.T*z]) # Also minimize over z. 

402 <Minimum of Concave Functions: min(∑(x), min_z xᵀ·z)> 

403 """ 

404 UAE = UncertainAffineExpression 

405 

406 if isinstance(lst, AffineExpression): 

407 return SumExtremes(lst, 1, largest=False, eigenvalues=False) 

408 elif isinstance(lst, Expression): 

409 raise TypeError("May only denote the minimum of a single affine " 

410 "expression or of multiple (concave) expressions.") 

411 

412 try: 

413 lst = [Constant(x) if not isinstance(x, Expression) else x for x in lst] 

414 except Exception as error: 

415 raise TypeError("Failed to convert some non-expression argument to a " 

416 "PICOS constant.") from error 

417 

418 if any(isinstance(x, UAE) for x in lst) \ 

419 and all(isinstance(x, (AffineExpression, UAE)) for x in lst) \ 

420 and all(x.certain or x.universe.distributional for x in lst): 

421 return RandomMinimumAffine(lst) 

422 else: 

423 return MinimumConcave(lst) 

424 

425 

426# ------------------------------------------------------------------------------ 

427# Functions that call expression methods. 

428# ------------------------------------------------------------------------------ 

429 

430 

431def _error_on_none(func): 

432 """Raise a :exc:`TypeError` if the function returns :obj:`None`.""" 

433 @functools.wraps(func) 

434 def wrapper(*args, **kwargs): 

435 result = func(*args, **kwargs) 

436 

437 if result is None: 

438 raise TypeError("PICOS does not have a representation for {}({})." 

439 .format(func.__qualname__ if hasattr(func, "__qualname__") else 

440 func.__name__, ", ".join([type(x).__name__ for x in args] + 

441 ["{}={}".format(k, type(x).__name__) for k, x in kwargs.items()] 

442 ))) 

443 

444 return result 

445 return wrapper 

446 

447 

448@_error_on_none 

449@convert_and_refine_arguments("x") 

450def exp(x): 

451 """Denote the exponential.""" 

452 if hasattr(x, "exp"): 

453 return x.exp 

454 

455 

456@_error_on_none 

457@convert_and_refine_arguments("x") 

458def log(x): 

459 """Denote the natural logarithm.""" 

460 if hasattr(x, "log"): 

461 return x.log 

462 

463 

464@deprecated("2.2", "Ensure that one operand is a PICOS expression and use infix" 

465 " @ instead.") 

466@_error_on_none 

467@convert_and_refine_arguments("x", "y") 

468def kron(x, y): 

469 """Denote the kronecker product.""" 

470 if hasattr(x, "kron"): 

471 return x @ y 

472 

473 

474@_error_on_none 

475@convert_and_refine_arguments("x") 

476def diag(x, n=1): 

477 r"""Form a diagonal matrix from the column-major vectorization of :math:`x`. 

478 

479 If :math:`n \neq 1`, then the vectorization is repeated :math:`n` times. 

480 """ 

481 if hasattr(x, "diag") and n == 1: 

482 return x.diag 

483 elif hasattr(x, "dupdiag"): 

484 return x.dupdiag(n) 

485 

486 

487@_error_on_none 

488@convert_and_refine_arguments("x") 

489def maindiag(x): 

490 """Extract the diagonal of :math:`x` as a column vector.""" 

491 if hasattr(x, "maindiag"): 

492 return x.maindiag 

493 

494 

495@_error_on_none 

496@convert_and_refine_arguments("x") 

497def trace(x): 

498 """Denote the trace of a square matrix.""" 

499 if hasattr(x, "tr"): 

500 return x.tr 

501 

502 

503@_error_on_none 

504@convert_and_refine_arguments("x") 

505def partial_trace(x, subsystems=0, dimensions=2, k=None, dim=None): 

506 """See :meth:`.exp_biaffine.BiaffineExpression.partial_trace`. 

507 

508 The parameters `k` and `dim` are for backwards compatibility. 

509 """ 

510 if k is not None: 

511 throw_deprecation_warning("Argument 'k' to partial_trace is " 

512 "deprecated: Use 'subsystems' instead.", decoratorLevel=2) 

513 subsystems = k 

514 

515 if dim is not None: 

516 throw_deprecation_warning("Argument 'dim' to partial_trace is " 

517 "deprecated: Use 'dimensions' instead.", decoratorLevel=2) 

518 dimensions = dim 

519 

520 if isinstance(x, BiaffineExpression): 

521 return x.partial_trace(subsystems, dimensions) 

522 

523 

524@_error_on_none 

525@convert_and_refine_arguments("x") 

526def partial_transpose(x, subsystems=0, dimensions=2, k=None, dim=None): 

527 """See :meth:`.exp_biaffine.BiaffineExpression.partial_transpose`. 

528 

529 The parameters `k` and `dim` are for backwards compatibility. 

530 """ 

531 if k is not None: 

532 throw_deprecation_warning("Argument 'k' to partial_transpose is " 

533 "deprecated: Use 'subsystems' instead.", decoratorLevel=2) 

534 subsystems = k 

535 

536 if dim is not None: 

537 throw_deprecation_warning("Argument 'dim' to partial_transpose is " 

538 "deprecated: Use 'dimensions' instead.", decoratorLevel=2) 

539 dimensions = dim 

540 

541 if isinstance(x, BiaffineExpression): 

542 return x.partial_transpose(subsystems, dimensions) 

543 

544 

545# ------------------------------------------------------------------------------ 

546# Alias functions for expression classes meant to be instanciated by the user. 

547# ------------------------------------------------------------------------------ 

548 

549 

550def _shorthand(name, cls): 

551 def shorthand(*args, **kwargs): 

552 return cls(*args, **kwargs) 

553 

554 shorthand.__doc__ = "Shorthand for :class:`{1} <{0}.{1}>`.".format( 

555 cls.__module__, cls.__qualname__) 

556 shorthand.__name__ = name 

557 shorthand.__qualname__ = name 

558 

559 return shorthand 

560 

561 

562expcone = _shorthand("expcone", ExponentialCone) 

563geomean = _shorthand("geomean", GeometricMean) 

564kldiv = _shorthand("kldiv", NegativeEntropy) 

565lse = _shorthand("lse", LogSumExp) 

566rsoc = _shorthand("rsoc", RotatedSecondOrderCone) 

567soc = _shorthand("soc", SecondOrderCone) 

568sumexp = _shorthand("sumexp", SumExponentials) 

569 

570 

571def sum_k_largest(x, k): 

572 """Wrapper for :class:`~picos.SumExtremes`. 

573 

574 Sets ``largest = True`` and ``eigenvalues = False``. 

575 

576 :Example: 

577 

578 >>> from picos import RealVariable, sum_k_largest 

579 >>> x = RealVariable("x", 5) 

580 >>> sum_k_largest(x, 2) 

581 <Sum of Largest Elements: sum_2_largest(x)> 

582 >>> sum_k_largest(x, 2) <= 2 

583 <Sum of Largest Elements Constraint: sum_2_largest(x) ≤ 2> 

584 """ 

585 return SumExtremes(x, k, largest=True, eigenvalues=False) 

586 

587 

588def sum_k_smallest(x, k): 

589 """Wrapper for :class:`~picos.SumExtremes`. 

590 

591 Sets ``largest = False`` and ``eigenvalues = False``. 

592 

593 :Example: 

594 

595 >>> from picos import RealVariable, sum_k_smallest 

596 >>> x = RealVariable("x", 5) 

597 >>> sum_k_smallest(x, 2) 

598 <Sum of Smallest Elements: sum_2_smallest(x)> 

599 >>> sum_k_smallest(x, 2) >= 2 

600 <Sum of Smallest Elements Constraint: sum_2_smallest(x) ≥ 2> 

601 """ 

602 return SumExtremes(x, k, largest=False, eigenvalues=False) 

603 

604 

605def lambda_max(x): 

606 """Wrapper for :class:`~picos.SumExtremes`. 

607 

608 Sets ``k = 1``, ``largest = True`` and ``eigenvalues = True``. 

609 

610 :Example: 

611 

612 >>> from picos import SymmetricVariable, lambda_max 

613 >>> X = SymmetricVariable("X", 5) 

614 >>> lambda_max(X) 

615 <Largest Eigenvalue: λ_max(X)> 

616 >>> lambda_max(X) <= 2 

617 <Largest Eigenvalue Constraint: λ_max(X) ≤ 2> 

618 """ 

619 return SumExtremes(x, 1, largest=True, eigenvalues=True) 

620 

621 

622def lambda_min(x): 

623 """Wrapper for :class:`~picos.SumExtremes`. 

624 

625 Sets ``k = 1``, ``largest = False`` and ``eigenvalues = True``. 

626 

627 :Example: 

628 

629 >>> from picos import SymmetricVariable, lambda_min 

630 >>> X = SymmetricVariable("X", 5) 

631 >>> lambda_min(X) 

632 <Smallest Eigenvalue: λ_min(X)> 

633 >>> lambda_min(X) >= 2 

634 <Smallest Eigenvalue Constraint: λ_min(X) ≥ 2> 

635 """ 

636 return SumExtremes(x, 1, largest=False, eigenvalues=True) 

637 

638 

639def sum_k_largest_lambda(x, k): 

640 """Wrapper for :class:`~picos.SumExtremes`. 

641 

642 Sets ``largest = True`` and ``eigenvalues = True``. 

643 

644 :Example: 

645 

646 >>> from picos import SymmetricVariable, sum_k_largest_lambda 

647 >>> X = SymmetricVariable("X", 5) 

648 >>> sum_k_largest_lambda(X, 2) 

649 <Sum of Largest Eigenvalues: sum_2_largest_λ(X)> 

650 >>> sum_k_largest_lambda(X, 2) <= 2 

651 <Sum of Largest Eigenvalues Constraint: sum_2_largest_λ(X) ≤ 2> 

652 """ 

653 return SumExtremes(x, k, largest=True, eigenvalues=True) 

654 

655 

656def sum_k_smallest_lambda(x, k): 

657 """Wrapper for :class:`~picos.SumExtremes`. 

658 

659 Sets ``largest = False`` and ``eigenvalues = True``. 

660 

661 :Example: 

662 

663 >>> from picos import SymmetricVariable, sum_k_smallest_lambda 

664 >>> X = SymmetricVariable("X", 5) 

665 >>> sum_k_smallest_lambda(X, 2) 

666 <Sum of Smallest Eigenvalues: sum_2_smallest_λ(X)> 

667 >>> sum_k_smallest_lambda(X, 2) >= 2 

668 <Sum of Smallest Eigenvalues Constraint: sum_2_smallest_λ(X) ≥ 2> 

669 """ 

670 return SumExtremes(x, k, largest=False, eigenvalues=True) 

671 

672 

673# ------------------------------------------------------------------------------ 

674# Legacy algebraic functions for backwards compatibility. 

675# ------------------------------------------------------------------------------ 

676 

677 

678def _deprecated_shorthand(name, cls, new_shorthand=None): 

679 uiRef = "picos.{}".format(new_shorthand if new_shorthand else cls.__name__) 

680 

681 # FIXME: Warning doesn't show the name of the deprecated shorthand function. 

682 @deprecated("2.0", useInstead=uiRef) 

683 def shorthand(*args, **kwargs): 

684 """|PLACEHOLDER|""" # noqa 

685 return cls(*args, **kwargs) 

686 

687 shorthand.__doc__ = shorthand.__doc__.replace("|PLACEHOLDER|", 

688 "Legacy shorthand for :class:`{1} <{0}.{1}>`.".format( 

689 cls.__module__, cls.__qualname__)) 

690 shorthand.__name__ = name 

691 shorthand.__qualname__ = name 

692 

693 return shorthand 

694 

695 

696ball = _deprecated_shorthand("ball", Ball) 

697detrootn = _deprecated_shorthand("detrootn", DetRootN) 

698kullback_leibler = _deprecated_shorthand( 

699 "kullback_leibler", NegativeEntropy, "kldiv") 

700logsumexp = _deprecated_shorthand("logsumexp", LogSumExp, "lse") 

701norm = _deprecated_shorthand("norm", Norm) 

702 

703 

704@deprecated("2.0", useInstead="picos.PowerTrace") 

705def tracepow(exp, num=1, denom=1, coef=None): 

706 """Legacy shorthand for :class:`~picos.PowerTrace`.""" 

707 return PowerTrace(exp, num / denom, coef) 

708 

709 

710@deprecated("2.0", useInstead="picos.Constant") 

711def new_param(name, value): 

712 """Create a constant or a list or dict or tuple thereof.""" 

713 if isinstance(value, list): 

714 # Handle a vector. 

715 try: 

716 for x in value: 

717 complex(x) 

718 except Exception: 

719 pass 

720 else: 

721 return Constant(name, value) 

722 

723 # Handle a matrix. 

724 if all(isinstance(x, list) for x in value) \ 

725 and all(len(x) == len(value[0]) for x in value): 

726 try: 

727 for x in value: 

728 for y in x: 

729 complex(y) 

730 except Exception: 

731 pass 

732 else: 

733 return Constant(name, value) 

734 

735 # Return a list of constants. 

736 return [Constant(glyphs.slice(name, i), x) for i, x in enumerate(value)] 

737 elif isinstance(value, tuple): 

738 # Return a list of constants. 

739 # NOTE: This is very inconsistent, but legacy behavior. 

740 return [Constant(glyphs.slice(name, i), x) for i, x in enumerate(value)] 

741 elif isinstance(value, dict): 

742 return {k: Constant(glyphs.slice(name, k), x) for k, x in value.items()} 

743 else: 

744 return Constant(name, value) 

745 

746 

747@deprecated("2.0", useInstead="picos.FlowConstraint") 

748def flow_Constraint(*args, **kwargs): 

749 """Legacy shorthand for :class:`~picos.FlowConstraint`.""" 

750 from ..constraints.con_flow import FlowConstraint 

751 return FlowConstraint(*args, **kwargs) 

752 

753 

754@deprecated("2.0", useInstead="picos.maindiag") 

755def diag_vect(x): 

756 """Extract the diagonal of :math:`x` as a column vector.""" 

757 return maindiag(x) 

758 

759 

760@deprecated("2.0", useInstead="picos.Simplex") 

761def simplex(gamma): 

762 r"""Create a standard simplex of radius :math:`\gamma`.""" 

763 return Simplex(gamma, truncated=False, symmetrized=False) 

764 

765 

766@deprecated("2.0", useInstead="picos.Simplex") 

767def truncated_simplex(gamma, sym=False): 

768 r"""Create a truncated simplex of radius :math:`\gamma`.""" 

769 return Simplex(gamma, truncated=True, symmetrized=sym) 

770 

771 

772# -------------------------------------- 

773__all__ = api_end(_API_START, globals())