Skip to content

Commit 9b619a2

Browse files
authored
Improve performance of ExpAtom, LogAtom, and EntropyAtom (#618)
1 parent bd90ea4 commit 9b619a2

File tree

4 files changed

+86
-21
lines changed

4 files changed

+86
-21
lines changed

src/atoms/EntropyAtom.jl

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,12 @@ entropy(x::AbstractExpr) = sum(EntropyAtom(x))
3636
entropy_elementwise(x::AbstractExpr) = EntropyAtom(x)
3737

3838
function new_conic_form!(context::Context, e::EntropyAtom)
39-
# -x log x >= t <=> x exp(t/x) <= 1 <==> (t,x,1) in exp cone
39+
# -x log(x) >= t <=> x exp(t/x) <= 1 <=> (t, x, 1) in ExponentialCone()
4040
x = e.children[1]
41-
m, n = size(x)
42-
t = Variable(m, n)
43-
for i in 1:m, j in 1:n
44-
f = vcat(t[i, j], x[i, j], 1)
45-
add_constraint!(context, GenericConstraint{MOI.ExponentialCone}(f))
46-
end
47-
return conic_form!(context, t)
41+
# To choose the permutation, we want the elements of the constraint to be
42+
# (t, x, 1) in ExponentialCone()
43+
# but with the identity permutation, the default is:
44+
# (x, 1, t) in ExponentialCone()
45+
# so [3, 1, 2] permutes it to the correct order.
46+
return _add_vectorized_exp_cone(context, x, [3, 1, 2])
4847
end

src/atoms/ExpAtom.jl

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,67 @@ evaluate(x::ExpAtom) = exp.(evaluate(x.children[1]))
3030
Base.exp(x::AbstractExpr) = ExpAtom(x)
3131

3232
function new_conic_form!(context::Context{T}, e::ExpAtom) where {T}
33-
# exp(x) \leq z <=> (x,1,z) \in ExpCone
33+
# exp(x) <= t <=> (x, 1, t) in ExponentialCone()
3434
x = e.children[1]
35+
x_tape = conic_form!(context, x)
36+
if x_tape isa SPARSE_VECTOR
37+
return exp.(x_tape)
38+
end
39+
return _add_vectorized_exp_cone(context, x, [1, 2, 3])
40+
end
41+
42+
"""
43+
_add_vectorized_exp_cone(
44+
context::Context{T},
45+
x,
46+
permutation::Vector{Int},
47+
) where {T}
48+
49+
Constrains `(x[i], 1, t[i]) ∈ ExponentialCone()` for each element of `x` and
50+
returns `t`.
51+
52+
Permutation is a permuted vector of `[1, 2, 3]` to reorder the triple before it
53+
is constrained. This is helpful for `LogAtom` and `EntropyAtom`.
54+
55+
## Motivation
56+
57+
A naive implementation of this method is:
58+
```julia
3559
m, n = size(x)
36-
z = Variable(m, n)
60+
t = Variable(m, n)
3761
for i in 1:m, j in 1:n
38-
f = vcat(x[i, j], 1, z[i, j])
62+
f = vcat(x[i, j], 1, t[i, j])[collect(permutation)]
3963
add_constraint!(context, GenericConstraint{MOI.ExponentialCone}(f))
4064
end
41-
return conic_form!(context, z)
65+
return conic_form!(context, t)
66+
end
67+
```
68+
This is slow because we are indexing on the Convex side, and Convex is based
69+
around vector/matrix operations. We don't want to produce n*m IndexAtoms!
70+
71+
Instead, we will drop to the MOI level to implement this in terms of scalar
72+
operations.
73+
"""
74+
function _add_vectorized_exp_cone(
75+
context::Context{T},
76+
x,
77+
permutation::Vector{Int},
78+
) where {T}
79+
@assert issetequal(permutation, (1, 2, 3))
80+
@assert length(permutation) == 3
81+
m, n = size(x)
82+
t = Variable(m, n)
83+
t_tape = conic_form!(context, t)
84+
ts = t_tape.variables::Vector{MOI.VariableIndex}
85+
x_tape = conic_form!(context, x)
86+
xs = MOI.Utilities.scalarize(to_vaf(x_tape))
87+
for (xi, ti) in zip(xs, ts)
88+
args = (xi, T(1), ti)[permutation]
89+
MOI.add_constraint(
90+
context.model,
91+
MOI.Utilities.operate(vcat, T, args...),
92+
MOI.ExponentialCone(),
93+
)
94+
end
95+
return t_tape
4296
end

src/atoms/LogAtom.jl

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,12 @@ evaluate(x::LogAtom) = log.(evaluate(x.children[1]))
3030
Base.log(x::AbstractExpr) = LogAtom(x)
3131

3232
function new_conic_form!(context::Context, e::LogAtom)
33-
# log(z) \geq x <=> (x,1,z) \in ExpCone
34-
z = e.children[1]
35-
m, n = size(z)
36-
x = Variable(m, n)
37-
for i in 1:m, j in 1:n
38-
f = vcat(x[i, j], 1, z[i, j])
39-
add_constraint!(context, GenericConstraint{MOI.ExponentialCone}(f))
40-
end
41-
return conic_form!(context, x)
33+
# log(x) >= t <=> (t, 1, x) in ExponentialCone()
34+
x = e.children[1]
35+
# To choose the permutation, we want the elements of the constraint to be:
36+
# (t, 1, x) in ExponentialCone()
37+
# The default is:
38+
# (x, 1, t) in ExponentialCone()
39+
# so [3, 2, 1] permutes it to the correct order.
40+
return _add_vectorized_exp_cone(context, x, [3, 2, 1])
4241
end

src/problem_depot/problems/exp.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@
2222
@test evaluate(exp(y)) 1 atol = atol rtol = rtol
2323
end
2424

25+
# Test for constant `exp` (#613)
26+
y = Variable()
27+
x = constant([1, 2, 3])
28+
p = minimize(sum(exp(x)) + y, y >= 0; numeric_type = T)
29+
if test
30+
@test problem_vexity(p) == ConvexVexity()
31+
end
32+
handle_problem!(p)
33+
if test
34+
@test p.optval sum(exp.([1, 2, 3])) atol = atol rtol = rtol
35+
@test evaluate(y) 0 atol = atol
36+
end
37+
2538
y = Variable()
2639
p = minimize(exp(y), y >= 1; numeric_type = T)
2740

0 commit comments

Comments
 (0)