# Source code for picos.expressions.cone_soc

# ------------------------------------------------------------------------------
# Copyright (C) 2019 Maximilian Stahlberg
# Based on the original picos.expressions module by Guillaume Sagnol.
#
# This file is part of PICOS.
#
# PICOS is free software: you can redistribute it and/or modify it under the
# 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 :class:SecondOrderCone."""

import operator
from collections import namedtuple

from .. import glyphs
from ..apidoc import api_end, api_start
from ..constraints import SOCConstraint
from .cone import Cone
from .exp_affine import AffineExpression

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

[docs]class SecondOrderCone(Cone):
r"""The second order cone.

.. _lorentz:

Also known as the quadratic, :math:2-norm, Lorentz, or ice cream cone.

For :math:n \in \mathbb{Z}_{\geq 2}, represents the convex cone

.. math::

\mathcal{Q}^n = \left\{
x \in \mathbb{R}^n
~\middle|~
x_1 \geq \sqrt{\sum_{i = 2}^n x_i^2}
\right\}.

:Dual cone:

The second order cone as defined above is self-dual.
"""

[docs]    def __init__(self, dim=None):
"""Construct a second order cone."""
if dim and dim < 2:
raise ValueError("The minimal dimension for {} is {}."
.format(self.__class__.__name__, 2))

typeStr = "Second Order Cone"
symbStr = glyphs.set(glyphs.sep(
glyphs.col_vectorize("t", "x"), glyphs.le(glyphs.norm("x"), "t")))

Cone.__init__(self, dim, typeStr, symbStr)

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):
from .uncertain import UncertainAffineExpression

assert isinstance(subtype, cls.Subtype)

if relation == operator.__rshift__:
if issubclass(other.clstype,
(AffineExpression, UncertainAffineExpression)) \
and not subtype.dim or subtype.dim == other.subtype.dim \
and other.subtype.dim >= 2:
if issubclass(other.clstype, UncertainAffineExpression):
raise NotImplementedError("Cannot predict the outcome "
"of constraining an uncertain affine expression to the "
"second order cone.")

return SOCConstraint.make_type(other.subtype.dim - 1)

return Cone._predict_base(cls, subtype, relation, other)

def _rshift_implementation(self, element):
from .uncertain import ConicPerturbationSet, UncertainAffineExpression

if isinstance(element, (AffineExpression, UncertainAffineExpression)):
self._check_dimension(element)

if len(element) < 2:
raise TypeError("Elements of the second order cone must be "
"at least two-dimensional.")

element = element.vec

if isinstance(element, AffineExpression):
return SOCConstraint(element[1:], element)
else:
if isinstance(element.universe, ConicPerturbationSet):
# Unpredictable case: Outcome depends on whether slices of
# the element remain uncertain.
return abs(element[1:]) <= element

# Handle scenario uncertainty for all cones.
return Cone._rshift_base(self, element)

@property
def dual_cone(self):
"""Implement :attr:.cone.Cone.dual_cone."""
return self

# --------------------------------------
__all__ = api_end(_API_START, globals())