r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

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"""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 @convert_operands(scalarRHS=True)

114 @refine_operands()

115 def __add__(self, other):

116 if isinstance(other, AffineExpression):

117 if not other.constant:

118 raise NotImplementedError("You may only add a constant term to "

119 "a nonconstant PICOS logarithm.")

121 log = Logarithm(self._x * math.exp(other.value))

122 log._typeStr = "Offset " + log._typeStr

123 log._symbStr = glyphs.clever_add(self.string, other.string)

125 return log

127 @convert_operands(scalarRHS=True)

128 @refine_operands()

129 def __radd__(self, other):

130 if isinstance(other, AffineExpression):

131 log = self.__add__(other)

132 # NOTE: __add__ always creates a fresh expression.

133 log._symbStr = glyphs.clever_add(other.string, self.string)

134 return log

135 else:

136 return NotImplemented

138 @convert_operands(scalarRHS=True)

139 @refine_operands()

140 def __sub__(self, other):

141 if isinstance(other, AffineExpression):

142 if not other.constant:

143 raise NotImplementedError("You may only substract a constant "

144 "term from a nonconstant PICOS logarithm.")

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

147 log._typeStr = "Offset " + log._typeStr

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

150 return log

152 @convert_operands(scalarRHS=True)

153 @refine_operands()

154 def __mul__(self, other):

155 from . import Entropy, NegativeEntropy

157 if isinstance(other, AffineExpression):

158 if other.is0:

159 return AffineExpression.zero()

160 elif other.is1:

161 # NOTE: We could return self here, but this is more consistent

162 # with other expressions' __mul__ methods.

163 return Logarithm(self._x)

164 elif other.equals(self._x):

165 return NegativeEntropy(self._x)

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

167 return Entropy(self._x)

168 else:

169 raise NotImplementedError(

170 "You may multiply {} only with {}, 0, or 1."

171 .format(self.string, glyphs.plsmns(self._x.string)))

172 else:

173 return NotImplemented

175 @convert_operands(scalarRHS=True)

176 @refine_operands()

177 def __rmul__(self, other):

178 if isinstance(other, AffineExpression):

179 return self.__mul__(other)

180 else:

181 return NotImplemented

183 # --------------------------------------------------------------------------

184 # Methods and properties that return expressions.

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

187 @property

188 def x(self):

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

190 return self._x

192 @property

193 def exp(self):

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

195 return self._x

197 # --------------------------------------------------------------------------

198 # Constraint-creating operators, and _predict.

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

201 @classmethod

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

203 assert isinstance(subtype, cls.Subtype)

205 if relation == operator.__ge__:

206 if issubclass(other.clstype, AffineExpression) \

207 and other.subtype.dim == 1:

208 return LogConstraint.make_type()

210 return NotImplemented

212 @convert_operands(scalarRHS=True)

213 @validate_prediction

214 @refine_operands()

215 def __ge__(self, other):

216 if isinstance(other, AffineExpression):

217 return LogConstraint(self, other)

218 else:

219 return NotImplemented

222# --------------------------------------

223__all__ = api_end(_API_START, globals())