Coverage for picos/constraints/con_geomean.py: 97.80%
91 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-15 14:21 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-15 14:21 +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
8# terms of the GNU General Public License as published by the Free Software
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:
111 P.add_constraint(
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())