Coverage for picos/expressions/exp_logarithm.py: 74.14%

116 statements

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

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

2# Copyright (C) 2019 Maximilian Stahlberg

3# Based on the original picos.expressions module by Guillaume Sagnol.

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"""Implements :class:`Logarithm`."""

22import math

23import operator

24from collections import namedtuple

26import cvxopt

28from .. import glyphs

29from ..apidoc import api_end, api_start

30from ..constraints import LogConstraint

31from .data import convert_and_refine_arguments, convert_operands

32from .exp_affine import AffineExpression

33from .expression import Expression, refine_operands, validate_prediction

35_API_START = api_start(globals())

36# -------------------------------

39class Logarithm(Expression):

40 r"""Logarithm of a scalar affine expression.

42 :Definition:

44 For a real scalar affine expression :math:`x`, this is :math:`\log(x)`.

46 .. warning::

48 When you pose a lower bound on a logarithm :math:`\log(x)`, then PICOS

49 enforces :math:`x \geq 0` through an auxiliary constraint during

50 solution search.

51 """

53 # --------------------------------------------------------------------------

54 # Initialization and factory methods.

55 # --------------------------------------------------------------------------

57 @convert_and_refine_arguments("x")

58 def __init__(self, x):

59 """Construct a :class:`Logarithm`.

61 :param x: The scalar affine expression :math:`x`.

62 :type x: ~picos.expressions.AffineExpression

63 """

64 if not isinstance(x, AffineExpression):

65 raise TypeError("Can only take the logarithm of a real affine "

66 "expression, not of {}.".format(type(x).__name__))

67 elif not x.scalar:

68 raise TypeError("Can only take the logarithm of a scalar expression"

69 "but {} is shaped {}.".format(x.string, glyphs.shape(x.shape)))

71 self._x = x

73 Expression.__init__(self, "Logarithm", glyphs.log(x.string))

75 # --------------------------------------------------------------------------

76 # Abstract method implementations and method overridings, except _predict.

77 # --------------------------------------------------------------------------

79 def _get_refined(self):

80 if self._x.constant:

81 return AffineExpression.from_constant(self.value, 1, self._symbStr)

82 else:

83 return self

85 Subtype = namedtuple("Subtype", ())

87 def _get_subtype(self):

88 return self.Subtype()

90 def _get_value(self):

91 value = cvxopt.matrix(self._x._get_value()) # Must be dense for log.

92 return cvxopt.log(value)

94 def _get_mutables(self):

95 return self._x._get_mutables()

97 def _is_convex(self):

98 return False

100 def _is_concave(self):

101 return True

103 def _replace_mutables(self, mapping):

104 return self.__class__(self._x._replace_mutables(mapping))

106 def _freeze_mutables(self, freeze):

107 return self.__class__(self._x._freeze_mutables(freeze))

109 # --------------------------------------------------------------------------

110 # Python special method implementations, except constraint-creating ones.

111 # --------------------------------------------------------------------------

113 @classmethod

114 def _add(cls, self, other, forward):

115 if isinstance(other, AffineExpression) and other.constant:

116 if other.is0:

117 return self

119 log = cls(self._x * math.exp(other.value))

120 log._typeStr = "Offset " + log._typeStr

122 if forward:

124 else:

127 return log

129 if forward:

131 else:

134 @convert_operands(scalarRHS=True)

135 @refine_operands()

139 @convert_operands(scalarRHS=True)

140 @refine_operands()

144 @convert_operands(scalarRHS=True)

145 @refine_operands()

146 def __sub__(self, other):

147 if isinstance(other, AffineExpression) and other.constant:

148 log = Logarithm(self._x / math.exp(other.value))

149 log._typeStr = "Offset " + log._typeStr

150 log._symbStr = glyphs.clever_sub(self.string, other.string)

152 return log

154 return Expression.__sub__(self, other)

156 @classmethod

157 def _mul(cls, self, other, forward):

158 from . import Entropy, NegativeEntropy

160 if isinstance(other, AffineExpression):

161 if other.is0:

162 return AffineExpression.zero()

163 elif other.is1:

164 return self

165 elif other.equals(self._x):

166 return NegativeEntropy(self._x)

167 elif other.equals(-self._x):

168 return Entropy(self._x)

170 if forward:

171 return Expression.__mul__(self, other)

172 else:

173 return Expression.__rmul__(self, other)

175 @convert_operands(scalarRHS=True)

176 @refine_operands()

177 def __mul__(self, other):

178 return Logarithm._mul(self, other, True)

180 @convert_operands(scalarRHS=True)

181 @refine_operands()

182 def __rmul__(self, other):

183 return Logarithm._mul(self, other, False)

185 # --------------------------------------------------------------------------

186 # Methods and properties that return expressions.

187 # --------------------------------------------------------------------------

189 @property

190 def x(self):

191 """The expression :math:`x`."""

192 return self._x

194 @property

195 def exp(self):

196 """The exponential of the logarithm, equal to :math:`x`."""

197 return self._x

199 # --------------------------------------------------------------------------

200 # Constraint-creating operators, and _predict.

201 # --------------------------------------------------------------------------

203 @classmethod

204 def _predict(cls, subtype, relation, other):

205 assert isinstance(subtype, cls.Subtype)

207 if relation == operator.__ge__:

208 if issubclass(other.clstype, AffineExpression) \

209 and other.subtype.dim == 1:

210 return LogConstraint.make_type()

212 return NotImplemented

214 @convert_operands(scalarRHS=True)

215 @validate_prediction

216 @refine_operands()

217 def __ge__(self, other):

218 if isinstance(other, AffineExpression):

219 return LogConstraint(self, other)

220 else:

221 return NotImplemented

224# --------------------------------------

225__all__ = api_end(_API_START, globals())