Source code for picos.expressions.cone_psd

# ------------------------------------------------------------------------------
# Copyright (C) 2020 Maximilian Stahlberg
#
# This file is part of PICOS.
#
# PICOS is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# PICOS is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------------------------------------------

"""Implements the positive semidefinite cone."""

import operator
from collections import namedtuple

from .. import glyphs
from ..apidoc import api_end, api_start
from ..constraints.uncertain import ScenarioUncertainConicConstraint
from .cone import Cone
from .exp_affine import AffineExpression, ComplexAffineExpression
from .expression import ExpressionType
from .uncertain import ScenarioPerturbationSet, UncertainAffineExpression

_API_START = api_start(globals())
# -------------------------------


[docs]class PositiveSemidefiniteCone(Cone): r"""The positive semidefinite cone. Unlike other :class:`cones <.cone.Cone>` which are defined only on :math:`\mathbb{K}^n`, this cone accepts both symmetric and hermitian matrices as well as their :attr:`special vectorization <.exp_biaffine.BiaffineExpression.svec>` as members. :Example: >>> from picos import Constant, PositiveSemidefiniteCone >>> R = Constant("R", range(16), (4, 4)) >>> S = R + R.T >>> S.shape (4, 4) >>> S.svec.shape (10, 1) >>> S.svec << PositiveSemidefiniteCone() # Constrain the matrix via svec(). <4×4 LMI Constraint: R + Rᵀ ≽ 0> >>> C = S << PositiveSemidefiniteCone(); C # Constrain the matrix directly. <4×4 LMI Constraint: R + Rᵀ ≽ 0> >>> C.conic_membership_form[0] # The conic form still refers to svec(). <10×1 Real Constant: svec(R + Rᵀ)> >>> C.conic_membership_form[1] <10-dim. Positive Semidefinite Cone: {svec(A) : xᵀ·A·x ≥ 0 ∀ x}> """
[docs] def __init__(self, dim=None): r"""Construct a :class:`PositiveSemidefiniteCone`. If a fixed dimensionality is given, this must be the dimensiona of the special vectorization. For a :math:`n \times n` matrix, this is :math:`\frac{n(n + 1)}{2}`. """ Cone.__init__(self, dim, "Positive Semidefinite Cone", glyphs.set(glyphs.sep(glyphs.svec("A"), glyphs.forall(glyphs.ge( glyphs.mul(glyphs.mul(glyphs.transp("x"), "A"), "x"), glyphs.scalar(0)), "x"))))
def _get_mutables(self): return frozenset() def _replace_mutables(self): return self Subtype = namedtuple("Subtype", ("dim",)) def _get_subtype(self): return self.Subtype(self.dim) @classmethod def _predict(cls, subtype, relation, other): assert isinstance(subtype, cls.Subtype) if relation == operator.__rshift__: if issubclass(other.clstype, ( ComplexAffineExpression, UncertainAffineExpression)): m, n = other.subtype.shape if m == n: svec_length = int(0.5*n*(n + 1)) if subtype.dim and subtype.dim != svec_length: return NotImplemented if issubclass(other.clstype, ComplexAffineExpression): # Other is already a square matrix. matrix = other else: # Predict the vector svec(other). vector = other.clstype.make_type( shape=(svec_length, 1), universe_type=other.subtype.universe_type) elif 1 in other.subtype.shape: if subtype.dim and subtype.dim != other.subtype.dim: return NotImplemented if issubclass(other.clstype, ComplexAffineExpression): # Predict the square matrix desvec(other). n = 0.5*((8*other.subtype.dim + 1)**0.5 - 1) if int(n) != n: return NotImplemented n = int(n) matrix = other.clstype.make_type( shape=(n, n), constant=other.subtype.constant, nonneg=other.subtype.nonneg) else: # Other is already a vector. vector = other else: return NotImplemented if issubclass(other.clstype, ComplexAffineExpression): zero = AffineExpression.make_type( shape=matrix.subtype.shape, constant=True, nonneg=True) return matrix.clstype._predict( matrix.subtype, operator.__rshift__, zero) elif issubclass( other.subtype.universe_type.clstype, ScenarioPerturbationSet): dim = vector.subtype.dim count = other.subtype.universe_type.subtype.scenario_count cone = ExpressionType(cls, subtype) return ScenarioUncertainConicConstraint.make_type( dim=dim, scenario_count=count, cone_type=cone) else: return NotImplemented return NotImplemented def _rshift_implementation(self, element): if isinstance(element, ( ComplexAffineExpression, UncertainAffineExpression)): if element.square: # Mimic _check_dimension. n = element.shape[0] d = int(0.5*n*(n + 1)) if self.dim and self.dim != d: raise TypeError( "The shape {} of {} implies a {}-dimensional " "svec-representation which does not match the fixed " "dimensionality {} of the cone {}.".format( glyphs.shape(element.shape), element.string, d, self.dim, self.string)) if isinstance(element, ComplexAffineExpression): return element >> 0 elif isinstance(element.universe, ScenarioPerturbationSet): return ScenarioUncertainConicConstraint(element.svec, self) else: raise TypeError("LMIs with uncertainty parameterized " "through a {} are not supported.".format( element.universe.__class__.__name__)) elif 1 in element.shape: self._check_dimension(element) if isinstance(element, ComplexAffineExpression): return element.desvec >> 0 elif isinstance(element.universe, ScenarioPerturbationSet): return ScenarioUncertainConicConstraint(element, self) else: raise TypeError("LMIs with uncertainty parameterized " "through a {} are not supported.".format( element.universe.__class__.__name__)) else: raise TypeError("The {} expression {} is neither square nor a " "vector so it cannot be constrained to be in the positive " "semidefinite cone.".format(element.shape, element.string)) return NotImplemented @property def dual_cone(self): """Implement :attr:`.cone.Cone.dual_cone`.""" return self
# -------------------------------------- __all__ = api_end(_API_START, globals())