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

Hot-keys on this page
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())