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

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# ------------------------------------------------------------------------------ 

18 

19"""Implementation of :class:`SquaredNormConstraint`.""" 

20 

21from collections import namedtuple 

22 

23from .. import glyphs 

24from ..apidoc import api_end, api_start 

25from .constraint import Constraint, ConstraintConversion 

26 

27_API_START = api_start(globals()) 

28# ------------------------------- 

29 

30 

31class SquaredNormConstraint(Constraint): 

32 """Upper bound on a squared Euclidean or Frobenius norm.""" 

33 

34 class ConicConversion(ConstraintConversion): 

35 """Upper bound on a squared norm to conic conversion.""" 

36 

37 @classmethod 

38 def predict(cls, subtype, options): 

39 """Implement :meth:`~.constraint.ConstraintConversion.predict`.""" 

40 from . import (AbsoluteValueConstraint, RSOCConstraint, 

41 SOCConstraint) 

42 

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) 

50 

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) 

58 

59 x = con.squaredNorm.fullroot 

60 y = con.upperBound 

61 

62 P = Problem() 

63 

64 if y.constant: 

65 value = y.value 

66 

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)) 

73 

74 root = AffineExpression.from_constant( 

75 value**0.5, (1, 1), glyphs.sqrt(y.string)) 

76 

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)) 

84 

85 return P 

86 

87 def __init__(self, squaredNorm, upperBound): 

88 """Construct a :class:`SquaredNormConstraint`. 

89 

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 

96 

97 assert isinstance(squaredNorm, SquaredNorm) 

98 assert isinstance(upperBound, AffineExpression) 

99 assert len(upperBound) == 1 

100 

101 self.squaredNorm = squaredNorm 

102 self.upperBound = upperBound 

103 

104 super(SquaredNormConstraint, self).__init__(self.squaredNorm._typeStr) 

105 

106 Subtype = namedtuple("Subtype", ("argdim", "constant_bound")) 

107 

108 def _subtype(self): 

109 return self.Subtype(self.squaredNorm.argdim, self.upperBound.constant) 

110 

111 @classmethod 

112 def _cost(cls, subtype): 

113 return subtype.argdim + 2 # RSOCC case. 

114 

115 def _expression_names(self): 

116 yield "squaredNorm" 

117 yield "upperBound" 

118 

119 def _str(self): 

120 return glyphs.le(self.squaredNorm.string, self.upperBound.string) 

121 

122 def _get_size(self): 

123 return (1, 1) 

124 

125 def _get_slack(self): 

126 return self.upperBound.value - self.squaredNorm.value 

127 

128 

129# -------------------------------------- 

130__all__ = api_end(_API_START, globals())