Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support sparsity #41

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const SUPPORTED_VECTOR_SETS = Union{
MOI.PositiveSemidefiniteConeTriangle,
}

const SUPPORTED_TERMINATION_STATUS = [MOI.LOCALLY_SOLVED, MOI.OPTIMAL, MOI.ALMOST_OPTIMAL]
akshay326 marked this conversation as resolved.
Show resolved Hide resolved

"""
diff_optimizer(optimizer_constructor)::Optimizer

Expand All @@ -61,7 +63,7 @@ julia> model.add_constraint(...)

julia> backward!(model) # for convex quadratic models

julia> backward!(model) # for convex conic models
julia> backward_conic!(model) # for convex conic models
```
"""
function diff_optimizer(optimizer_constructor)::Optimizer
Expand Down Expand Up @@ -176,7 +178,7 @@ function MOI.optimize!(model::Optimizer)
solution = MOI.optimize!(model.optimizer)

# do not fail. interferes with MOI.Tests.linear12test
if MOI.get(model.optimizer, MOI.TerminationStatus()) in [MOI.LOCALLY_SOLVED, MOI.OPTIMAL]
if MOI.get(model.optimizer, MOI.TerminationStatus()) in SUPPORTED_TERMINATION_STATUS
# save the solution
model.primal_optimal = MOI.get(model.optimizer, MOI.VariablePrimal(), model.var_idx)
model.dual_optimal = MOI.get(model.optimizer, MOI.ConstraintDual(), model.con_idx)
Expand Down Expand Up @@ -271,9 +273,9 @@ function backward!(model::Optimizer, params::Array{String}, dl_dz::Array{Float64

grads = []
LHS = create_LHS_matrix(z, λ, Q, G, h, A)
RHS = [dl_dz'; zeros(neq+nineq,1)]
RHS = sparse([dl_dz'; zeros(neq+nineq,1)])
akshay326 marked this conversation as resolved.
Show resolved Hide resolved

partial_grads = -(LHS \ RHS)
partial_grads = -lsqr(LHS, RHS) # error rate for lsqr should be small comparison to (LHS \ RHS)

dz = partial_grads[1:nz]
if nineq > 0
Expand Down Expand Up @@ -520,7 +522,7 @@ end
Find projection of vectors in `v` on product of `cones`.
For more info, refer https://github.com/matbesancon/MathOptSetDistances.jl
"""
π(cones, v) = MOSD.projection_on_set(MOSD.DefaultDistance(), v, cones)
π(cones, v) = sparse(MOSD.projection_on_set(MOSD.DefaultDistance(), v, cones))


"""
Expand All @@ -529,7 +531,7 @@ For more info, refer https://github.com/matbesancon/MathOptSetDistances.jl
Find gradient of projection of vectors in `v` on product of `cones`.
For more info, refer https://github.com/matbesancon/MathOptSetDistances.jl
"""
Dπ(cones, v) = MOSD.projection_gradient_on_set(MOSD.DefaultDistance(), v, cones)
Dπ(cones, v) = sparse(MOSD.projection_gradient_on_set(MOSD.DefaultDistance(), v, cones))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if we force sparsity here or where Dπ is used

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep. i just wanted to test how much we can improve on benchmarking. projections aren't that sparse, and ideally MOSD should return sparse values

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forcing sparsity of the type if the projection itself isn't sparse will result in poor performance

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, removing it from here


"""
backward_conic!(model::Optimizer, dA::Array{Float64,2}, db::Array{Float64}, dc::Array{Float64})
Expand All @@ -541,7 +543,7 @@ but it this *does returns* the actual jacobians.
For theoretical background, refer Section 3 of Differentiating Through a Cone Program, https://arxiv.org/abs/1904.09043
"""
function backward_conic!(model::Optimizer, dA::Array{Float64,2}, db::Array{Float64}, dc::Array{Float64})
if MOI.get(model, MOI.TerminationStatus()) in [MOI.LOCALLY_SOLVED, MOI.OPTIMAL]
if MOI.get(model, MOI.TerminationStatus()) in SUPPORTED_TERMINATION_STATUS
@assert MOI.get(model.optimizer, MOI.SolverName()) == "SCS"
MOIU.load_variables(model.optimizer.model.optimizer, MOI.get(model, MOI.NumberOfVariables()))

Expand Down Expand Up @@ -596,31 +598,35 @@ function backward_conic!(model::Optimizer, dA::Array{Float64,2}, db::Array{Float
z = (x, y - s, [1.0])
u, v, w = z

Q = [
# reshaping vectors to matrix
b = b[:, :]
c = c[:, :]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need these two lines?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reindex/convert SparseVector to SparseMatrix. turns out SparseVector's give error on multiplication


Q = sparse([
zeros(n,n) A' c;
akshay326 marked this conversation as resolved.
Show resolved Hide resolved
-A zeros(m,m) b;
-c' -b' zeros(1,1)
]
])

# find gradient of projections on dual of the cones
Dπv = Dπ([MOI.dual_set(cone) for cone in cones], v)

M = ((Q - Matrix{Float64}(I, size(Q))) * BlockDiagonal([Matrix{Float64}(I, length(u), length(u)), Dπv, Matrix{Float64}(I,1,1)])) + Matrix{Float64}(I, size(Q))
M = ((Q - sparse(1.0I, size(Q))) * blockdiag(sparse(1.0I, length(u), length(u)), Dπv, sparse(1.0I, 1, 1))) + sparse(1.0I, size(Q))

# find projections on dual of the cones
πz = vcat([u, π([MOI.dual_set(cone) for cone in cones], v), max.(w,0.0)]...)
πz = sparse(vcat([u, π([MOI.dual_set(cone) for cone in cones], v), max.(w,0.0)]...))

dQ = [
dQ = sparse([
zeros(n,n) dA' dc;
akshay326 marked this conversation as resolved.
Show resolved Hide resolved
-dA zeros(m,m) db;
-dc' -db' zeros(1,1)
]
])
Copy link
Collaborator

@matbesancon matbesancon Sep 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this part is doing a copy for nothing

if:

[
    a  b  c
    a  b  c
]

creates an already sparse matrix, then sparse(result) will do a copy for nothing. If the result is not sparse, a dense array is built out of the sparse arrays, which is then re-converted to sparse.
It would be better to create an empty dQ with spzeros(n+m+1, n+m+1) and then fill the non-diagonal blocks
with
dQ[n+1:n+1+m,1:n] .= -dA
etc.

Same thing above for the creation of Q

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh. thanks for pointing out. never realized this redundant sparse-dense-sparse conversion

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the spzeros(1,1) can be replaced by just 0

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


RHS = dQ * πz
if norm(RHS) <= 1e-4
dz = zeros(Float64, size(RHS))
dz = sparse(0.0I, size(RHS[:, :]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this [:,:] ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, again RHS turns out to be a SparseVector

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed it

else
dz = lsqr(M, RHS)
dz = lsqr(M, RHS) # supports sparse already
end

du, dv, dw = dz[1:n], dz[n+1:n+m], dz[n+m+1]
Expand Down
14 changes: 9 additions & 5 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,19 @@ Helper method while calling [`backward!`](@ref)
"""
function create_LHS_matrix(z, λ, Q, G, h, A=nothing)
if A == nothing || size(A)[1] == 0
return [Q G' * Diagonal(λ);
G Diagonal(G * z - h)]
return sparse([
Q G' * Diagonal(λ);
G Diagonal(G * z - h)
])
else
@assert size(A)[2] == size(G)[2]
p, n = size(A)
m = size(G)[1]
return [Q G' * Diagonal(λ) A';
G Diagonal(G * z - h) zeros(m, p);
A zeros(p, m) zeros(p, p)]
return sparse([
Q G' * Diagonal(λ) A';
G Diagonal(G * z - h) zeros(m, p);
A zeros(p, m) zeros(p, p)
])
end
end

Expand Down