# Coverage for picos/constraints/con_geomean.py: 97.80%

## 91 statements

, created at 2023-03-26 07:46 +0000

1# ------------------------------------------------------------------------------

2# Copyright (C) 2012-2017 Guillaume Sagnol

3# Copyright (C) 2018-2019 Maximilian Stahlberg

4#

5# This file is part of PICOS.

6#

7# PICOS is free software: you can redistribute it and/or modify it under the

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"""Implementation of :class:`GeometricMeanConstraint`."""

22import math

23from collections import namedtuple

25from .. import glyphs

26from ..apidoc import api_end, api_start

27from .constraint import Constraint, ConstraintConversion

29_API_START = api_start(globals())

30# -------------------------------

33class GeometricMeanConstraint(Constraint):

34 """Lower bound on a geometric mean."""

36 class RSOCConversion(ConstraintConversion):

37 """Geometric mean to rotated second order cone constraint conversion."""

39 @classmethod

40 def predict(cls, subtype, options):

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

42 from ..expressions import RealVariable

43 from . import RSOCConstraint

45 j = subtype.argdim - 1

46 k = int(math.log(j, 2))

47 l = j + k - "{:b}".format(j - 2**k).count("1")

49 yield ("var", RealVariable.make_var_type(dim=1, bnd=0), l - 1)

50 yield ("con", RSOCConstraint.make_type(argdim=1), l)

52 # TODO: Refactor to be human readable.

53 @classmethod

54 def convert(cls, con, options):

55 """Implement :meth:`~.constraint.ConstraintConversion.convert`."""

56 from ..expressions.algebra import rsoc

57 from ..modeling import Problem

59 geoMean = con.geoMean

60 lowerBound = con.lowerBound

62 P = Problem()

63 x = geoMean.x

64 m = len(x)

65 lm = [[i] for i in range(m - 1, -1, -1)]

66 K = []

67 depth = 0

68 u = {}

70 while len(lm) > 1:

71 depth += 1

72 nlm = []

73 while lm:

74 i1 = lm.pop()[-1]

75 if lm:

76 i2 = lm.pop()[0]

77 else:

78 i2 = 'x'

79 nlm.insert(0, (i2, i1))

80 k = str(depth) + ':' + str(i1) + '-' + str(i2)

81 K.append(k)

82 u[k] = P.add_variable('__u[' + k + ']', 1)

83 lm = nlm

85 root = K[-1]

86 maxd = int(K[-1].split(':')[0])

87 P.remove_variable(u[root].name) # TODO: Update for new expressions.

88 u[root] = lowerBound

90 for k in K:

91 i1 = int(k.split('-')[0].split(':')[1])

92 i2 = k.split('-')[1]

93 if i2 != 'x':

94 i2 = int(i2)

95 if k[:2] == '1:':

96 if i2 != 'x':

97 P.add_constraint((x[i1] & x[i2] & u[k]) << rsoc())

98 else:

99 P.add_constraint((x[i1] & lowerBound & u[k]) << rsoc())

100 else:

101 d = int(k.split(':')[0])

102 if i2 == 'x' and d < maxd:

103 k2pot = [ki for ki in K

104 if ki.startswith(str(d - 1) + ':')

105 and int(ki.split(':')[1].split('-')[0]) >= i1]

106 k1 = k2pot[0]

107 if len(k2pot) == 2:

108 k2 = k2pot[1]

109 P.add_constraint((u[k1] & u[k2] & u[k]) << rsoc())

110 else:

112 (u[k1] & lowerBound & u[k]) << rsoc())

113 else:

114 k1 = [ki for ki in K

115 if ki.startswith(str(d - 1) + ':' + str(i1))][0]

116 k2 = [ki for ki in K if ki.startswith(str(d - 1) + ':')

117 and ki.endswith('-' + str(i2))][0]

118 P.add_constraint((u[k1] & u[k2] & u[k]) << rsoc())

120 return P

122 def __init__(self, geoMean, lowerBound):

123 """Construct a :class:`GeometricMeanConstraint`.

125 :param ~picos.expressions.GeometricMean lhs:

126 Constrained expression.

127 :param ~picos.expressions.AffineExpression lowerBound:

128 Lower bound on the expression.

129 """

130 from ..expressions import AffineExpression, GeometricMean

132 assert isinstance(geoMean, GeometricMean)

133 assert isinstance(lowerBound, AffineExpression)

134 assert len(lowerBound) == 1

136 self.geoMean = geoMean

137 self.lowerBound = lowerBound

139 super(GeometricMeanConstraint, self).__init__(geoMean._typeStr)

141 Subtype = namedtuple("Subtype", ("argdim",))

143 def _subtype(self):

144 return self.Subtype(len(self.geoMean.x))

146 @classmethod

147 def _cost(cls, subtype):

148 return subtype.argdim + 1

150 def _expression_names(self):

151 yield "geoMean"

152 yield "lowerBound"

154 def _str(self):

155 return glyphs.ge(self.geoMean.string, self.lowerBound.string)

157 def _get_slack(self):

158 return self.geoMean.safe_value - self.lowerBound.safe_value

161# --------------------------------------

162__all__ = api_end(_API_START, globals())