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 if any(not isinstance(expression, Expression) for expression in lst): 

104 return builtins.sum(lst) 

105 

106 if len(lst) == 0: 

107 return Constant(0) 

108 elif len(lst) == 1: 

109 return lst[0] 

110 elif len(lst) == 2: 

111 return lst[0] + lst[1] 

112 

113 theSum = lst[0] 

114 for expression in lst[1:]: 

115 theSum += expression 

116 

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

118 

119 return theSum 

120 

121 

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

123 """Create an affine block matrix expression. 

124 

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

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

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

128 each expression or constant represents one block. 

129 

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

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

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

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

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

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

136 

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

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

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

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

141 

142 :param nested: 

143 The blocks. 

144 :type nested: 

145 tuple(tuple) or list(list) 

146 

147 :param shapes: 

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

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

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

151 :type shapes: 

152 tuple(tuple) or list(list) 

153 

154 :param str name: 

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

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

157 

158 :Example: 

159 

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

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

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

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

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

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

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

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

168 >>> print(A) 

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

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

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

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

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

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

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

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

177 >>> print(B) 

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

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

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

181 [ 1.00e+00 0.00e+00 0.00e+00] 

182 [ 0.00e+00 1.00e+00 0.00e+00] 

183 """ 

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

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

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

187 for stage in range(2): 

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

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

190 for row in nested], dtype=int) 

191 

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

193 for i, Ri in enumerate(R): 

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

195 

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

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

198 

199 if len(m) > 1: 

200 raise TypeError( 

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

202 .format(i + 1, m)) 

203 elif len(m) == 1: 

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

205 else: 

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

207 M.append(None) 

208 

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

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

211 for row in nested], dtype=int) 

212 

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

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

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

216 

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

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

219 

220 if len(n) > 1: 

221 raise TypeError( 

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

223 .format(j + 1, n)) 

224 elif len(n) == 1: 

225 N.append(n.pop()) 

226 else: 

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

228 N.append(None) 

229 

230 if stage == 0: 

231 nested = [[ 

232 x if isinstance(x, BiaffineExpression) 

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

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

235 

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

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

238 

239 # Find the common base type of all expressions. 

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

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

242 typecode = basetype._get_typecode() 

243 

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

245 M, N = tuple(M), tuple(N) 

246 

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

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

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

250 

251 # Compute the full matrix dimensions. 

252 m = builtins.sum(M) 

253 n = builtins.sum(N) 

254 mn = m*n 

255 

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

257 MLen = len(N) 

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

259 blockOffsets = tuple( 

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

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

262 

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

264 def _I(): 

265 for k, block in enumerate(blocks): 

266 rows, cols = block.shape 

267 i, j = blockOffsets[k] 

268 for c in range(cols): 

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

270 yield range(columnOffset, columnOffset + rows) 

271 

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

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

274 # column-major vectorization of the full matrix. 

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

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

277 J = range(mn) 

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

279 

280 # Obtain all coefficient keys. 

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

282 

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

284 coefs = {} 

285 for mtbs in keys: 

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

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

288 

289 coefs[mtbs] = T*cvxopt_vcat([ 

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

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

292 for block in blocks]) 

293 

294 # Build the string description. 

295 if name: 

296 string = str(name) 

297 elif len(blocks) > 9: 

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

299 else: 

300 string = functools.reduce( 

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

302 (functools.reduce( 

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

304 (block.string for block in blockRow)) 

305 for blockRow in nested)) 

306 

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

308 

309 

310def max(lst): 

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

312 

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

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

315 

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

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

318 implicitly goes over their perturbation parameters as well. 

319 

320 :param lst: 

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

322 :type lst: 

323 list or tuple or ~picos.expressions.AffineExpression 

324 

325 :Example: 

326 

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

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

329 >>> max(x) 

330 <Largest Element: max(x)> 

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

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

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

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

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

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

337 >>> from picos.uncertain import UnitBallPerturbationSet 

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

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

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

341 """ 

342 UAE = UncertainAffineExpression 

343 

344 if isinstance(lst, AffineExpression): 

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

346 elif isinstance(lst, Expression): 

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

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

349 elif any(isinstance(x, UAE) for x in lst) \ 

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

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

352 return RandomMaximumAffine(lst) 

353 else: 

354 return MaximumConvex(lst) 

355 

356 

357def min(lst): 

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

359 

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

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

362 

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

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

365 implicitly goes over their perturbation parameters as well. 

366 

367 :param lst: 

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

369 :type lst: 

370 list or tuple or ~picos.expressions.AffineExpression 

371 

372 :Example: 

373 

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

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

376 >>> min(x) 

377 <Smallest Element: min(x)> 

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

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

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

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

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

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

384 >>> from picos.uncertain import UnitBallPerturbationSet 

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

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

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

388 """ 

389 UAE = UncertainAffineExpression 

390 

391 if isinstance(lst, AffineExpression): 

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

393 elif isinstance(lst, Expression): 

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

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

396 elif any(isinstance(x, UAE) for x in lst) \ 

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

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

399 return RandomMinimumAffine(lst) 

400 else: 

401 return MinimumConcave(lst) 

402 

403 

404# ------------------------------------------------------------------------------ 

405# Functions that call expression methods. 

406# ------------------------------------------------------------------------------ 

407 

408 

409def _error_on_none(func): 

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

411 @functools.wraps(func) 

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

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

414 

415 if result is None: 

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

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

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

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

420 ))) 

421 

422 return result 

423 return wrapper 

424 

425 

426@_error_on_none 

427@convert_and_refine_arguments("x") 

428def exp(x): 

429 """Denote the exponential.""" 

430 if hasattr(x, "exp"): 

431 return x.exp 

432 

433 

434@_error_on_none 

435@convert_and_refine_arguments("x") 

436def log(x): 

437 """Denote the natural logarithm.""" 

438 if hasattr(x, "log"): 

439 return x.log 

440 

441 

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

443 " @ instead.") 

444@_error_on_none 

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

446def kron(x, y): 

447 """Denote the kronecker product.""" 

448 if hasattr(x, "kron"): 

449 return x @ y 

450 

451 

452@_error_on_none 

453@convert_and_refine_arguments("x") 

454def diag(x, n=1): 

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

456 

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

458 """ 

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

460 return x.diag 

461 elif hasattr(x, "dupdiag"): 

462 return x.dupdiag(n) 

463 

464 

465@_error_on_none 

466@convert_and_refine_arguments("x") 

467def maindiag(x): 

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

469 if hasattr(x, "maindiag"): 

470 return x.maindiag 

471 

472 

473@_error_on_none 

474@convert_and_refine_arguments("x") 

475def trace(x): 

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

477 if hasattr(x, "tr"): 

478 return x.tr 

479 

480 

481@_error_on_none 

482@convert_and_refine_arguments("x") 

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

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

485 

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

487 """ 

488 if k is not None: 

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

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

491 subsystems = k 

492 

493 if dim is not None: 

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

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

496 dimensions = dim 

497 

498 if isinstance(x, BiaffineExpression): 

499 return x.partial_trace(subsystems, dimensions) 

500 

501 

502@_error_on_none 

503@convert_and_refine_arguments("x") 

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

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

506 

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

508 """ 

509 if k is not None: 

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

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

512 subsystems = k 

513 

514 if dim is not None: 

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

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

517 dimensions = dim 

518 

519 if isinstance(x, BiaffineExpression): 

520 return x.partial_transpose(subsystems, dimensions) 

521 

522 

523# ------------------------------------------------------------------------------ 

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

525# ------------------------------------------------------------------------------ 

526 

527 

528def _shorthand(name, cls): 

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

530 return cls(*args, **kwargs) 

531 

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

533 cls.__module__, cls.__qualname__) 

534 shorthand.__name__ = name 

535 shorthand.__qualname__ = name 

536 

537 return shorthand 

538 

539 

540expcone = _shorthand("expcone", ExponentialCone) 

541geomean = _shorthand("geomean", GeometricMean) 

542kldiv = _shorthand("kldiv", NegativeEntropy) 

543lse = _shorthand("lse", LogSumExp) 

544rsoc = _shorthand("rsoc", RotatedSecondOrderCone) 

545soc = _shorthand("soc", SecondOrderCone) 

546sumexp = _shorthand("sumexp", SumExponentials) 

547 

548 

549def sum_k_largest(x, k): 

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

551 

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

553 

554 :Example: 

555 

556 >>> from picos import RealVariable, sum_k_largest 

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

558 >>> sum_k_largest(x, 2) 

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

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

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

562 """ 

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

564 

565 

566def sum_k_smallest(x, k): 

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

568 

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

570 

571 :Example: 

572 

573 >>> from picos import RealVariable, sum_k_smallest 

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

575 >>> sum_k_smallest(x, 2) 

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

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

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

579 """ 

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

581 

582 

583def lambda_max(x): 

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

585 

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

587 

588 :Example: 

589 

590 >>> from picos import SymmetricVariable, lambda_max 

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

592 >>> lambda_max(X) 

593 <Largest Eigenvalue: λ_max(X)> 

594 >>> lambda_max(X) <= 2 

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

596 """ 

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

598 

599 

600def lambda_min(x): 

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

602 

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

604 

605 :Example: 

606 

607 >>> from picos import SymmetricVariable, lambda_min 

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

609 >>> lambda_min(X) 

610 <Smallest Eigenvalue: λ_min(X)> 

611 >>> lambda_min(X) >= 2 

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

613 """ 

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

615 

616 

617def sum_k_largest_lambda(x, k): 

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

619 

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

621 

622 :Example: 

623 

624 >>> from picos import SymmetricVariable, sum_k_largest_lambda 

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

626 >>> sum_k_largest_lambda(X, 2) 

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

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

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

630 """ 

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

632 

633 

634def sum_k_smallest_lambda(x, k): 

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

636 

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

638 

639 :Example: 

640 

641 >>> from picos import SymmetricVariable, sum_k_smallest_lambda 

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

643 >>> sum_k_smallest_lambda(X, 2) 

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

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

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

647 """ 

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

649 

650 

651# ------------------------------------------------------------------------------ 

652# Legacy algebraic functions for backwards compatibility. 

653# ------------------------------------------------------------------------------ 

654 

655 

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

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

658 

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

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

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

662 """|PLACEHOLDER|""" # noqa 

663 return cls(*args, **kwargs) 

664 

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

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

667 cls.__module__, cls.__qualname__)) 

668 shorthand.__name__ = name 

669 shorthand.__qualname__ = name 

670 

671 return shorthand 

672 

673 

674ball = _deprecated_shorthand("ball", Ball) 

675detrootn = _deprecated_shorthand("detrootn", DetRootN) 

676kullback_leibler = _deprecated_shorthand( 

677 "kullback_leibler", NegativeEntropy, "kldiv") 

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

679norm = _deprecated_shorthand("norm", Norm) 

680 

681 

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

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

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

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

686 

687 

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

689def new_param(name, value): 

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

691 if isinstance(value, list): 

692 # Handle a vector. 

693 try: 

694 for x in value: 

695 complex(x) 

696 except Exception: 

697 pass 

698 else: 

699 return Constant(name, value) 

700 

701 # Handle a matrix. 

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

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

704 try: 

705 for x in value: 

706 for y in x: 

707 complex(y) 

708 except Exception: 

709 pass 

710 else: 

711 return Constant(name, value) 

712 

713 # Return a list of constants. 

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

715 elif isinstance(value, tuple): 

716 # Return a list of constants. 

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

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

719 elif isinstance(value, dict): 

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

721 else: 

722 return Constant(name, value) 

723 

724 

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

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

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

728 from ..constraints.con_flow import FlowConstraint 

729 return FlowConstraint(*args, **kwargs) 

730 

731 

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

733def diag_vect(x): 

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

735 return maindiag(x) 

736 

737 

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

739def simplex(gamma): 

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

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

742 

743 

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

745def truncated_simplex(gamma, sym=False): 

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

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

748 

749 

750# -------------------------------------- 

751__all__ = api_end(_API_START, globals())