1"""
2Copyright 2013 Steven Diamond
3
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9    http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16"""
17
18from typing import List, Tuple
19
20import numpy as np
21
22from cvxpy.constraints.constraint import Constraint
23from cvxpy.expressions import cvxtypes
24from cvxpy.utilities import scopes
25
26
27class ExpCone(Constraint):
28    """A reformulated exponential cone constraint.
29
30    Operates elementwise on :math:`x, y, z`.
31
32    Original cone:
33
34    .. math::
35
36        K = \\{(x,y,z) \\mid y > 0, ye^{x/y} <= z\\}
37            \\cup \\{(x,y,z) \\mid x \\leq 0, y = 0, z \\geq 0\\}
38
39    Reformulated cone:
40
41    .. math::
42
43        K = \\{(x,y,z) \\mid y, z > 0, y\\log(y) + x \\leq y\\log(z)\\}
44             \\cup \\{(x,y,z) \\mid x \\leq 0, y = 0, z \\geq 0\\}
45
46    Parameters
47    ----------
48    x : Variable
49        x in the exponential cone.
50    y : Variable
51        y in the exponential cone.
52    z : Variable
53        z in the exponential cone.
54    """
55
56    def __init__(self, x, y, z, constr_id=None) -> None:
57        Expression = cvxtypes.expression()
58        self.x = Expression.cast_to_const(x)
59        self.y = Expression.cast_to_const(y)
60        self.z = Expression.cast_to_const(z)
61        super(ExpCone, self).__init__([self.x, self.y, self.z],
62                                      constr_id)
63
64    def __str__(self) -> str:
65        return "ExpCone(%s, %s, %s)" % (self.x, self.y, self.z)
66
67    def __repr__(self) -> str:
68        return "ExpCone(%s, %s, %s)" % (self.x, self.y, self.z)
69
70    @property
71    def residual(self):
72        # TODO(akshayka): The projection should be implemented directly.
73        from cvxpy import Minimize, Problem, Variable, hstack, norm2
74        if self.x.value is None or self.y.value is None or self.z.value is None:
75            return None
76        x = Variable(self.x.shape)
77        y = Variable(self.y.shape)
78        z = Variable(self.z.shape)
79        constr = [ExpCone(x, y, z)]
80        obj = Minimize(norm2(hstack([x, y, z]) -
81                             hstack([self.x.value, self.y.value, self.z.value])))
82        problem = Problem(obj, constr)
83        return problem.solve()
84
85    @property
86    def size(self) -> int:
87        """The number of entries in the combined cones.
88        """
89        return 3 * self.num_cones()
90
91    def num_cones(self):
92        """The number of elementwise cones.
93        """
94        return self.x.size
95
96    def cone_sizes(self) -> List[int]:
97        """The dimensions of the exponential cones.
98
99        Returns
100        -------
101        list
102            A list of the sizes of the elementwise cones.
103        """
104        return [3]*self.num_cones()
105
106    def is_dcp(self, dpp: bool = False) -> bool:
107        """An exponential constraint is DCP if each argument is affine.
108        """
109        if dpp:
110            with scopes.dpp_scope():
111                return all(arg.is_affine() for arg in self.args)
112        return all(arg.is_affine() for arg in self.args)
113
114    def is_dgp(self, dpp: bool = False) -> bool:
115        return False
116
117    def is_dqcp(self) -> bool:
118        return self.is_dcp()
119
120    @property
121    def shape(self) -> Tuple[int, ...]:
122        s = (3,) + self.x.shape
123        return s
124
125    def save_dual_value(self, value) -> None:
126        # TODO(akshaya,SteveDiamond): verify that reshaping below works correctly
127        value = np.reshape(value, newshape=(-1, 3))
128        dv0 = np.reshape(value[:, 0], newshape=self.x.shape)
129        dv1 = np.reshape(value[:, 1], newshape=self.y.shape)
130        dv2 = np.reshape(value[:, 2], newshape=self.z.shape)
131        self.dual_variables[0].save_value(dv0)
132        self.dual_variables[1].save_value(dv1)
133        self.dual_variables[2].save_value(dv2)
134