Coverage for picos/constraints/con_sqnorm.py: 96.61%
59 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-26 07:46 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-26 07:46 +0000
1# ------------------------------------------------------------------------------
2# Copyright (C) 2020 Maximilian Stahlberg
3#
4# This file is part of PICOS.
5#
6# PICOS is free software: you can redistribute it and/or modify it under the
7# terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# PICOS is distributed in the hope that it will be useful, but WITHOUT ANY
12# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# this program. If not, see <http://www.gnu.org/licenses/>.
17# ------------------------------------------------------------------------------
19"""Implementation of :class:`SquaredNormConstraint`."""
21from collections import namedtuple
23from .. import glyphs
24from ..apidoc import api_end, api_start
25from .constraint import Constraint, ConstraintConversion
27_API_START = api_start(globals())
28# -------------------------------
31class SquaredNormConstraint(Constraint):
32 """Upper bound on a squared Euclidean or Frobenius norm."""
34 class ConicConversion(ConstraintConversion):
35 """Upper bound on a squared norm to conic conversion."""
37 @classmethod
38 def predict(cls, subtype, options):
39 """Implement :meth:`~.constraint.ConstraintConversion.predict`."""
40 from . import (AbsoluteValueConstraint, RSOCConstraint,
41 SOCConstraint)
43 if subtype.constant_bound:
44 if subtype.argdim == 1:
45 yield ("con", AbsoluteValueConstraint.make_type(), 1)
46 else:
47 yield ("con", SOCConstraint.make_type(subtype.argdim), 1)
48 else:
49 yield ("con", RSOCConstraint.make_type(subtype.argdim), 1)
51 @classmethod
52 def convert(cls, con, options):
53 """Implement :meth:`~.constraint.ConstraintConversion.convert`."""
54 from ..expressions import AffineExpression
55 from ..modeling import Problem
56 from . import (AbsoluteValueConstraint, RSOCConstraint,
57 SOCConstraint)
59 x = con.squaredNorm.fullroot
60 y = con.upperBound
62 P = Problem()
64 if y.constant:
65 value = y.value
67 if value < 0:
68 # TODO: Reconsider whether infeasible constraints should
69 # raise an exception during conversion.
70 raise ValueError("The constraint {} is infeasible as it "
71 "upper-bounds a squared norm by a negative constant."
72 .format(con))
74 root = AffineExpression.from_constant(
75 value**0.5, (1, 1), glyphs.sqrt(y.string))
77 if len(x) == 1:
78 P.add_constraint(AbsoluteValueConstraint(x, root))
79 else:
80 P.add_constraint(SOCConstraint(x, root))
81 else:
82 one = AffineExpression.from_constant(1)
83 P.add_constraint(RSOCConstraint(x, y, one))
85 return P
87 def __init__(self, squaredNorm, upperBound):
88 """Construct a :class:`SquaredNormConstraint`.
90 :param ~picos.expressions.SquaredNorm squaredNorm:
91 The squared norm to bound from above.
92 :param ~picos.expressions.AffineExpression upperBound:
93 Upper bound on the squared norm.
94 """
95 from ..expressions import AffineExpression, SquaredNorm
97 assert isinstance(squaredNorm, SquaredNorm)
98 assert isinstance(upperBound, AffineExpression)
99 assert len(upperBound) == 1
101 self.squaredNorm = squaredNorm
102 self.upperBound = upperBound
104 super(SquaredNormConstraint, self).__init__(self.squaredNorm._typeStr)
106 Subtype = namedtuple("Subtype", ("argdim", "constant_bound"))
108 def _subtype(self):
109 return self.Subtype(self.squaredNorm.argdim, self.upperBound.constant)
111 @classmethod
112 def _cost(cls, subtype):
113 return subtype.argdim + 2 # RSOCC case.
115 def _expression_names(self):
116 yield "squaredNorm"
117 yield "upperBound"
119 def _str(self):
120 return glyphs.le(self.squaredNorm.string, self.upperBound.string)
122 def _get_size(self):
123 return (1, 1)
125 def _get_slack(self):
126 return self.upperBound.value - self.squaredNorm.value
129# --------------------------------------
130__all__ = api_end(_API_START, globals())