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 

62@functools.lru_cache() 

63def O(rows=1, cols=1): # noqa 

64 """Create a zero matrix. 

65 

66 :Example: 

67 

68 >>> from picos import O 

69 >>> print(O(2, 3)) 

70 [0 0 0] 

71 [0 0 0] 

72 """ 

73 return AffineExpression.zero((rows, cols)) 

74 

75 

76@functools.lru_cache() 

77def I(size=1): # noqa 

78 """Create an identity matrix. 

79 

80 :Example: 

81 

82 >>> from picos import I 

83 >>> print(I(3)) 

84 [ 1.00e+00 0 0 ] 

85 [ 0 1.00e+00 0 ] 

86 [ 0 0 1.00e+00] 

87 """ 

88 return AffineExpression.from_constant("I", (size, size)) 

89 

90 

91@functools.lru_cache() 

92def J(rows=1, cols=1): 

93 """Create a matrix of all ones. 

94 

95 :Example: 

96 

97 >>> from picos import J 

98 >>> print(J(2, 3)) 

99 [ 1.00e+00 1.00e+00 1.00e+00] 

100 [ 1.00e+00 1.00e+00 1.00e+00] 

101 """ 

102 return AffineExpression.from_constant(1, (rows, cols)) 

103 

104 

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

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

107 

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

109 representations when summing PICOS expressions. 

110 

111 :param lst: A list of 

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

113 single affine expression whose elements shall be summed. 

114 :type lst: list or tuple or 

115 ~picos.expressions.ComplexAffineExpression 

116 

117 :param it: DEPRECATED 

118 :param indices: DEPRECATED 

119 

120 :Example: 

121 

122 >>> import builtins 

123 >>> import picos 

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

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

126 >>> builtins.sum(e) 

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

128 >>> picos.sum(e) 

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

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

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

132 """ 

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

134 # Deprecated as of 2.0. 

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

136 "are deprecated and ignored.") 

137 

138 if isinstance(lst, Expression): 

139 if isinstance(lst, BiaffineExpression): 

140 return (lst | 1.0) 

141 else: 

142 raise TypeError( 

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

144 .format(type(lst).__name__)) 

145 

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

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

148 return builtins.sum(lst) 

149 

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

151 try: 

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

153 except Exception as error: 

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

155 "PICOS constant.") from error 

156 

157 if len(lst) == 0: 

158 return Constant(0) 

159 elif len(lst) == 1: 

160 return lst[0] 

161 elif len(lst) == 2: 

162 return lst[0] + lst[1] 

163 

164 theSum = lst[0] 

165 for expression in lst[1:]: 

166 theSum += expression 

167 

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

169 

170 return theSum 

171 

172 

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

174 """Create an affine block matrix expression. 

175 

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

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

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

179 each expression or constant represents one block. 

180 

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

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

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

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

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

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

187 

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

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

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

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

192 

193 :param nested: 

194 The blocks. 

195 :type nested: 

196 tuple(tuple) or list(list) 

197 

198 :param shapes: 

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

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

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

202 :type shapes: 

203 tuple(tuple) or list(list) 

204 

205 :param str name: 

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

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

208 

209 :Example: 

210 

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

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

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

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

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

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

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

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

219 >>> print(A) 

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

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

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

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

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

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

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

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

228 >>> print(B) 

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

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

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

232 [ 1.00e+00 0.00e+00 0.00e+00] 

233 [ 0.00e+00 1.00e+00 0.00e+00] 

234 """ 

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

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

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

238 for stage in range(2): 

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

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

241 for row in nested], dtype=int) 

242 

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

244 for i, Ri in enumerate(R): 

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

246 

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

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

249 

250 if len(m) > 1: 

251 raise TypeError( 

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

253 .format(i + 1, m)) 

254 elif len(m) == 1: 

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

256 else: 

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

258 M.append(None) 

259 

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

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

262 for row in nested], dtype=int) 

263 

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

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

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

267 

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

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

270 

271 if len(n) > 1: 

272 raise TypeError( 

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

274 .format(j + 1, n)) 

275 elif len(n) == 1: 

276 N.append(n.pop()) 

277 else: 

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

279 N.append(None) 

280 

281 if stage == 0: 

282 nested = [[ 

283 x if isinstance(x, BiaffineExpression) 

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

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

286 

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

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

289 

290 # Find the common base type of all expressions. 

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

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

293 typecode = basetype._get_typecode() 

294 

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

296 M, N = tuple(M), tuple(N) 

297 

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

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

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

301 

302 # Compute the full matrix dimensions. 

303 m = builtins.sum(M) 

304 n = builtins.sum(N) 

305 mn = m*n 

306 

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

308 MLen = len(N) 

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

310 blockOffsets = tuple( 

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

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

313 

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

315 def _I(): 

316 for k, block in enumerate(blocks): 

317 rows, cols = block.shape 

318 i, j = blockOffsets[k] 

319 for c in range(cols): 

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

321 yield range(columnOffset, columnOffset + rows) 

322 

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

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

325 # column-major vectorization of the full matrix. 

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

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

328 J = range(mn) 

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

330 

331 # Obtain all coefficient keys. 

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

333 

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

335 coefs = {} 

336 for mtbs in keys: 

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

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

339 

340 coefs[mtbs] = T*cvxopt_vcat([ 

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

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

343 for block in blocks]) 

344 

345 # Build the string description. 

346 if name: 

347 string = str(name) 

348 elif len(blocks) > 9: 

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

350 else: 

351 string = functools.reduce( 

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

353 (functools.reduce( 

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

355 (block.string for block in blockRow)) 

356 for blockRow in nested)) 

357 

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

359 

360 

361def max(lst): 

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

363 

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

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

366 

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

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

369 implicitly goes over their perturbation parameters as well. 

370 

371 :param lst: 

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

373 :type lst: 

374 list or tuple or ~picos.expressions.AffineExpression 

375 

376 :Example: 

377 

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

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

380 >>> max(x) 

381 <Largest Element: max(x)> 

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

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

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

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

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

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

388 >>> from picos.uncertain import UnitBallPerturbationSet 

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

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

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

392 """ 

393 UAE = UncertainAffineExpression 

394 

395 if isinstance(lst, AffineExpression): 

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

397 elif isinstance(lst, Expression): 

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

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

400 

401 try: 

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

403 except Exception as error: 

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

405 "PICOS constant.") from error 

406 

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

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

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

410 return RandomMaximumAffine(lst) 

411 else: 

412 return MaximumConvex(lst) 

413 

414 

415def min(lst): 

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

417 

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

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

420 

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

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

423 implicitly goes over their perturbation parameters as well. 

424 

425 :param lst: 

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

427 :type lst: 

428 list or tuple or ~picos.expressions.AffineExpression 

429 

430 :Example: 

431 

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

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

434 >>> min(x) 

435 <Smallest Element: min(x)> 

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

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

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

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

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

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

442 >>> from picos.uncertain import UnitBallPerturbationSet 

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

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

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

446 """ 

447 UAE = UncertainAffineExpression 

448 

449 if isinstance(lst, AffineExpression): 

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

451 elif isinstance(lst, Expression): 

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

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

454 

455 try: 

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

457 except Exception as error: 

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

459 "PICOS constant.") from error 

460 

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

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

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

464 return RandomMinimumAffine(lst) 

465 else: 

466 return MinimumConcave(lst) 

467 

468 

469# ------------------------------------------------------------------------------ 

470# Functions that call expression methods. 

471# ------------------------------------------------------------------------------ 

472 

473 

474def _error_on_none(func): 

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

476 @functools.wraps(func) 

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

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

479 

480 if result is None: 

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

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

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

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

485 ))) 

486 

487 return result 

488 return wrapper 

489 

490 

491@_error_on_none 

492@convert_and_refine_arguments("x") 

493def exp(x): 

494 """Denote the exponential.""" 

495 if hasattr(x, "exp"): 

496 return x.exp 

497 

498 

499@_error_on_none 

500@convert_and_refine_arguments("x") 

501def log(x): 

502 """Denote the natural logarithm.""" 

503 if hasattr(x, "log"): 

504 return x.log 

505 

506 

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

508 " @ instead.") 

509@_error_on_none 

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

511def kron(x, y): 

512 """Denote the kronecker product.""" 

513 if hasattr(x, "kron"): 

514 return x @ y 

515 

516 

517@_error_on_none 

518@convert_and_refine_arguments("x") 

519def diag(x, n=1): 

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

521 

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

523 """ 

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

525 return x.diag 

526 elif hasattr(x, "dupdiag"): 

527 return x.dupdiag(n) 

528 

529 

530@_error_on_none 

531@convert_and_refine_arguments("x") 

532def maindiag(x): 

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

534 if hasattr(x, "maindiag"): 

535 return x.maindiag 

536 

537 

538@_error_on_none 

539@convert_and_refine_arguments("x") 

540def trace(x): 

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

542 if hasattr(x, "tr"): 

543 return x.tr 

544 

545 

546@_error_on_none 

547@convert_and_refine_arguments("x") 

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

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

550 

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

552 """ 

553 if k is not None: 

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

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

556 subsystems = k 

557 

558 if dim is not None: 

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

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

561 dimensions = dim 

562 

563 if isinstance(x, BiaffineExpression): 

564 return x.partial_trace(subsystems, dimensions) 

565 

566 

567@_error_on_none 

568@convert_and_refine_arguments("x") 

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

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

571 

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

573 """ 

574 if k is not None: 

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

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

577 subsystems = k 

578 

579 if dim is not None: 

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

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

582 dimensions = dim 

583 

584 if isinstance(x, BiaffineExpression): 

585 return x.partial_transpose(subsystems, dimensions) 

586 

587 

588# ------------------------------------------------------------------------------ 

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

590# ------------------------------------------------------------------------------ 

591 

592 

593def _shorthand(name, cls): 

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

595 return cls(*args, **kwargs) 

596 

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

598 cls.__module__, cls.__qualname__) 

599 shorthand.__name__ = name 

600 shorthand.__qualname__ = name 

601 

602 return shorthand 

603 

604 

605expcone = _shorthand("expcone", ExponentialCone) 

606geomean = _shorthand("geomean", GeometricMean) 

607kldiv = _shorthand("kldiv", NegativeEntropy) 

608lse = _shorthand("lse", LogSumExp) 

609rsoc = _shorthand("rsoc", RotatedSecondOrderCone) 

610soc = _shorthand("soc", SecondOrderCone) 

611sumexp = _shorthand("sumexp", SumExponentials) 

612 

613 

614def sum_k_largest(x, k): 

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

616 

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

618 

619 :Example: 

620 

621 >>> from picos import RealVariable, sum_k_largest 

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

623 >>> sum_k_largest(x, 2) 

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

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

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

627 """ 

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

629 

630 

631def sum_k_smallest(x, k): 

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

633 

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

635 

636 :Example: 

637 

638 >>> from picos import RealVariable, sum_k_smallest 

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

640 >>> sum_k_smallest(x, 2) 

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

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

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

644 """ 

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

646 

647 

648def lambda_max(x): 

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

650 

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

652 

653 :Example: 

654 

655 >>> from picos import SymmetricVariable, lambda_max 

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

657 >>> lambda_max(X) 

658 <Largest Eigenvalue: λ_max(X)> 

659 >>> lambda_max(X) <= 2 

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

661 """ 

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

663 

664 

665def lambda_min(x): 

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

667 

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

669 

670 :Example: 

671 

672 >>> from picos import SymmetricVariable, lambda_min 

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

674 >>> lambda_min(X) 

675 <Smallest Eigenvalue: λ_min(X)> 

676 >>> lambda_min(X) >= 2 

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

678 """ 

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

680 

681 

682def sum_k_largest_lambda(x, k): 

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

684 

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

686 

687 :Example: 

688 

689 >>> from picos import SymmetricVariable, sum_k_largest_lambda 

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

691 >>> sum_k_largest_lambda(X, 2) 

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

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

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

695 """ 

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

697 

698 

699def sum_k_smallest_lambda(x, k): 

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

701 

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

703 

704 :Example: 

705 

706 >>> from picos import SymmetricVariable, sum_k_smallest_lambda 

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

708 >>> sum_k_smallest_lambda(X, 2) 

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

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

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

712 """ 

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

714 

715 

716# ------------------------------------------------------------------------------ 

717# Legacy algebraic functions for backwards compatibility. 

718# ------------------------------------------------------------------------------ 

719 

720 

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

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

723 

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

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

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

727 """|PLACEHOLDER|""" # noqa 

728 return cls(*args, **kwargs) 

729 

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

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

732 cls.__module__, cls.__qualname__)) 

733 shorthand.__name__ = name 

734 shorthand.__qualname__ = name 

735 

736 return shorthand 

737 

738 

739ball = _deprecated_shorthand("ball", Ball) 

740detrootn = _deprecated_shorthand("detrootn", DetRootN) 

741kullback_leibler = _deprecated_shorthand( 

742 "kullback_leibler", NegativeEntropy, "kldiv") 

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

744norm = _deprecated_shorthand("norm", Norm) 

745 

746 

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

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

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

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

751 

752 

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

754def new_param(name, value): 

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

756 if isinstance(value, list): 

757 # Handle a vector. 

758 try: 

759 for x in value: 

760 complex(x) 

761 except Exception: 

762 pass 

763 else: 

764 return Constant(name, value) 

765 

766 # Handle a matrix. 

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

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

769 try: 

770 for x in value: 

771 for y in x: 

772 complex(y) 

773 except Exception: 

774 pass 

775 else: 

776 return Constant(name, value) 

777 

778 # Return a list of constants. 

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

780 elif isinstance(value, tuple): 

781 # Return a list of constants. 

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

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

784 elif isinstance(value, dict): 

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

786 else: 

787 return Constant(name, value) 

788 

789 

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

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

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

793 from ..constraints.con_flow import FlowConstraint 

794 return FlowConstraint(*args, **kwargs) 

795 

796 

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

798def diag_vect(x): 

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

800 return maindiag(x) 

801 

802 

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

804def simplex(gamma): 

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

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

807 

808 

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

810def truncated_simplex(gamma, sym=False): 

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

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

813 

814 

815# -------------------------------------- 

816__all__ = api_end(_API_START, globals())