Coverage for picos/expressions/algebra.py : 83.88%

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# ------------------------------------------------------------------------------
20"""Implements functions that create or modify algebraic expressions."""
22import builtins
23import functools
24import itertools
26import cvxopt
27import numpy
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
53_API_START = api_start(globals())
54# -------------------------------
57# ------------------------------------------------------------------------------
58# Algebraic functions with a logic of their own.
59# ------------------------------------------------------------------------------
62def sum(lst, it=None, indices=None):
63 """Sum PICOS expressions and give the result a meaningful description.
65 This is a replacement for Python's :func:`sum` that produces sensible string
66 representations when summing PICOS expressions.
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
74 :param it: DEPRECATED
75 :param indices: DEPRECATED
77 :Example:
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.")
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__))
103 if any(not isinstance(expression, Expression) for expression in lst):
104 return builtins.sum(lst)
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]
113 theSum = lst[0]
114 for expression in lst[1:]:
115 theSum += expression
117 theSum._symbStr = glyphs.sum(arguments([exp.string for exp in lst]))
119 return theSum
122def block(nested, shapes=None, name=None):
123 """Create an affine block matrix expression.
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.
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.
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).
142 :param nested:
143 The blocks.
144 :type nested:
145 tuple(tuple) or list(list)
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)
154 :param str name:
155 Name or string description of the resulting block matrix. If
156 :obj:`None`, a descriptive string will be generated.
158 :Example:
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)
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)])
196 if shapes and shapes[0][i]:
197 m.add(shapes[0][i])
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)
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)
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)])
217 if shapes and shapes[1][j]:
218 n.add(shapes[1][j])
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)
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)]
236 # List the blocks in block-row-major order.
237 blocks = [block for blockRow in nested for block in blockRow]
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()
244 # Allow constant time random access to the block dimensions.
245 M, N = tuple(M), tuple(N)
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))
251 # Compute the full matrix dimensions.
252 m = builtins.sum(M)
253 n = builtins.sum(N)
254 mn = m*n
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)))
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)
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)
280 # Obtain all coefficient keys.
281 keys = set(key for block in blocks for key in block._coefs.keys())
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)
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])
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))
307 return basetype(string, (m, n), coefs)
310def max(lst):
311 """Denote the maximum over a collection of convex scalar expressions.
313 If instead of a collection of expressions only a single multidimensional
314 affine expression is given, this denotes its largest element instead.
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.
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
325 :Example:
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
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)
357def min(lst):
358 """Denote the minimum over a collection of concave scalar expressions.
360 If instead of a collection of expressions only a single multidimensional
361 affine expression is given, this denotes its smallest element instead.
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.
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
372 :Example:
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
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)
404# ------------------------------------------------------------------------------
405# Functions that call expression methods.
406# ------------------------------------------------------------------------------
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)
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 )))
422 return result
423 return wrapper
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
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
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
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`.
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)
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
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
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`.
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
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
498 if isinstance(x, BiaffineExpression):
499 return x.partial_trace(subsystems, dimensions)
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`.
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
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
519 if isinstance(x, BiaffineExpression):
520 return x.partial_transpose(subsystems, dimensions)
523# ------------------------------------------------------------------------------
524# Alias functions for expression classes meant to be instanciated by the user.
525# ------------------------------------------------------------------------------
528def _shorthand(name, cls):
529 def shorthand(*args, **kwargs):
530 return cls(*args, **kwargs)
532 shorthand.__doc__ = "Shorthand for :class:`{1} <{0}.{1}>`.".format(
533 cls.__module__, cls.__qualname__)
534 shorthand.__name__ = name
535 shorthand.__qualname__ = name
537 return shorthand
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)
549def sum_k_largest(x, k):
550 """Wrapper for :class:`~picos.SumExtremes`.
552 Sets ``largest = True`` and ``eigenvalues = False``.
554 :Example:
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)
566def sum_k_smallest(x, k):
567 """Wrapper for :class:`~picos.SumExtremes`.
569 Sets ``largest = False`` and ``eigenvalues = False``.
571 :Example:
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)
583def lambda_max(x):
584 """Wrapper for :class:`~picos.SumExtremes`.
586 Sets ``k = 1``, ``largest = True`` and ``eigenvalues = True``.
588 :Example:
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)
600def lambda_min(x):
601 """Wrapper for :class:`~picos.SumExtremes`.
603 Sets ``k = 1``, ``largest = False`` and ``eigenvalues = True``.
605 :Example:
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)
617def sum_k_largest_lambda(x, k):
618 """Wrapper for :class:`~picos.SumExtremes`.
620 Sets ``largest = True`` and ``eigenvalues = True``.
622 :Example:
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)
634def sum_k_smallest_lambda(x, k):
635 """Wrapper for :class:`~picos.SumExtremes`.
637 Sets ``largest = False`` and ``eigenvalues = True``.
639 :Example:
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)
651# ------------------------------------------------------------------------------
652# Legacy algebraic functions for backwards compatibility.
653# ------------------------------------------------------------------------------
656def _deprecated_shorthand(name, cls, new_shorthand=None):
657 uiRef = "picos.{}".format(new_shorthand if new_shorthand else cls.__name__)
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)
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
671 return shorthand
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)
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)
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)
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)
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)
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)
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)
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)
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)
750# --------------------------------------
751__all__ = api_end(_API_START, globals())