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

Add MathProgBase solver interface. #9

Merged
merged 23 commits into from
Feb 13, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0d9ac6c
Add MathProgBase solver interface.
tkoolen Feb 1, 2018
4f14108
Add MathProgBase solver interface.
tkoolen Feb 1, 2018
c27d244
Address review comments.
tkoolen Feb 5, 2018
df2f7e0
Copy over relevant parts of MathProgBase test/quadprog.jl.
tkoolen Feb 5, 2018
9b061b6
Refine/implement more of MathProgBase interface.
tkoolen Feb 5, 2018
de19e17
Upper bound on MPB.
tkoolen Feb 5, 2018
1510ae3
Added mathprog tests. Basic restructuring. Still need to complete the…
bstellato Feb 2, 2018
bce0012
Merged both changes but still need to finish the interface
bstellato Feb 6, 2018
4311dfd
Deleted solver interface in mathprog old file
bstellato Feb 6, 2018
ff8f248
Improve test coverage.
tkoolen Feb 6, 2018
938a50b
Add more tests, fix objective bug.
tkoolen Feb 7, 2018
683a6e5
Merged tkoolen changes
bstellato Feb 7, 2018
381c761
Added changes for proper updates without performing setup again. Trie…
bstellato Feb 7, 2018
14e0d97
Added update_settings! to setparameters
bstellato Feb 7, 2018
f4b4af9
First tests working but still getting segfaults when I close julia
bstellato Feb 9, 2018
7a46756
Apparently setwarmstart! causes troubles
bstellato Feb 9, 2018
23f3202
Got Twan tests working
bstellato Feb 9, 2018
ec0ab49
Got more tests working. Need to fix last ones in linproginterface
bstellato Feb 9, 2018
ed9b220
Fixed some bugs
bstellato Feb 12, 2018
1299cc5
Got all mathprogbase tests to work. Fixed dual variables signs
bstellato Feb 12, 2018
d11c474
Other adjustments based on comments
bstellato Feb 12, 2018
5094552
Fixed setquadobj! to reset problem status forcing a new solve
bstellato Feb 12, 2018
47f7010
Edited changelog before merging
bstellato Feb 13, 2018
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
5 changes: 5 additions & 0 deletions src/OSQP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ __precompile__()

module OSQP

export OSQPSolver

# Compatibility stuff
using Compat
using Compat.SparseArrays
Expand Down Expand Up @@ -34,5 +36,8 @@ end
include("constants.jl")
include("types.jl")
include("interface.jl")
include("OSQPSolverInterface.jl")

import .OSQPSolverInterface: OSQPSolver

end # module
186 changes: 186 additions & 0 deletions src/OSQPSolverInterface.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
module OSQPSolverInterface

using MathProgBase
import OSQP

import MathProgBase: numvar, numconstr

struct OSQPSolver <: MathProgBase.AbstractMathProgSolver
settings::Dict{Symbol,Any}
end

OSQPSolver() = OSQPSolver(Dict{Symbol, Any}())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe name this OSQPMPBSolver to avoid the imminent conflict with MOI?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see this is still the common approach in most of the current MathProgBase interfaces, e.g., EcosSolverInterface.jl.

@mlubin Are you planning to change the other solvers as well or to just drop the MathProgBase interfaces altogether?

Copy link
Collaborator

Choose a reason for hiding this comment

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

MPB interfaces will all be moved into separate packages when we start releasing the new MOI interfaces. It's up to developer discretion whether the MOI interface will sit in the same package as the direct solver wrapper or in a separate package.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Then it could be better to move this interface to a separate package called MathProgBaseOSQP.jl as done in MathProgBaseMosek.jl. In this way we could keep OSQPSolver in both MPB and MOI interfaces and avoid using both them at the same time. Is this the purpose?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems a bit silly to create a package just for 200 lines of code that are going to be deprecated soon. (I'm reconsidering if we should do this for all solvers.) If we remove the line:

import .OSQPSolverInterface: OSQPSolver

then there's nothing much to worry about with having both MPB and MOI interfaces in the same package.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Renamed the module to OSQPMathProgBaseInterface (solver is still named OSQPSolver). No longer exporting OSQPSolver, but exporting OSQPMathProgBaseInterface instead. What do you think?


mutable struct OSQPModel <: MathProgBase.AbstractLinearQuadraticModel
settings::Dict{Symbol, Any}
inner::OSQP.Model

P::SparseMatrixCSC{Float64, Int}
q::Vector{Float64}
A::SparseMatrixCSC{Float64, Int}
l::Vector{Float64}
u::Vector{Float64}

results::OSQP.Results

function OSQPModel(settings::Associative{Symbol, Any})
P = spzeros(Float64, 0, 0)
q = Float64[]
A = spzeros(Float64, 0, 0)
l = Float64[]
u = Float64[]
new(settings, OSQP.Model(), P, q, A, l, u) # leave results undefined
end
end

function Base.resize!(model::OSQPModel, n, m)
nchange = n != numvar(model)
mchange = m != numconstr(model)
if nchange
model.P = spzeros(Float64, n, n)
resize!(model.q, n)
end
if mchange
resize!(model.l, m)
resize!(model.u, m)
end
if nchange || mchange
model.A = spzeros(Float64, m, n)
end

model
end

checksolved(model::OSQPModel) = isdefined(model, :results) || error("Model has not been solved.")
variablebounderror() = error("Variable bounds are not supported (use constraints instead).")

# http://mathprogbasejl.readthedocs.io/en/latest/solverinterface.html
MathProgBase.LinearQuadraticModel(solver::OSQPSolver) = OSQPModel(solver.settings)
MathProgBase.getsolution(model::OSQPModel) = (checksolved(model); model.results.x)
MathProgBase.getobjval(model::OSQPModel) = (checksolved(model); model.results.info.obj_val)

function MathProgBase.optimize!(model::OSQPModel)
OSQP.setup!(model.inner; P = model.P, q = model.q, A = model.A, l = model.l, u = model.u, settings = model.settings)
model.results = OSQP.solve!(model.inner)
end

function MathProgBase.status(model::OSQPModel)::Symbol
checksolved(model)
status = model.results.info.status
ret = status # if OSQP status can't be mapped to a standard return value, just return as is

# map to standard return status values:
status == :Solved && (ret = :Optimal)
status == :Max_iter_reached && (ret = :UserLimit)
status == :Interrupted && (ret = :UserLimit) # following Gurobi.jl
status == :Primal_infeasible && (ret = :Infeasible)
status == :Primal_infeasible_inaccurate && (ret = :Infeasible)
status == :Dual_infeasible && (ret = :DualityFailure)

return ret
end

# TODO: getobjbound
# TODO: getobjgap
MathProgBase.getrawsolver(model::OSQPModel) = model.inner
MathProgBase.getsolvetime(model::OSQPModel) = (checksolved(model); model.results.info.run_time)
# TODO: setsense!
# TODO: getsense
MathProgBase.numvar(model::OSQPModel) = size(model.A, 2)
MathProgBase.numconstr(model::OSQPModel) = size(model.A, 1)
# TODO: freemodel!
# TODO: copy
MathProgBase.setvartype!(model::OSQPModel, v::Vector{Symbol}) = any(x -> x != :Cont, v) && error("OSQP only supports continuous variables.")
MathProgBase.getvartype(model::OSQPModel) = fill(:Cont, numvar(model))

function MathProgBase.setparameters!(x::Union{OSQPSolver, OSQPModel}; Silent = nothing)
if Silent != nothing
Silent::Bool
x.settings[:verbose] = !Silent
end
end

MathProgBase.setwarmstart!(model::OSQPModel, v) = OSQP.warm_start!(model.inner, x = v)


# http://mathprogbasejl.readthedocs.io/en/latest/lpqcqp.html#linearquadratic-models
# TODO: loadproblem!(m::AbstractLinearQuadraticModel, filename::String)

function MathProgBase.loadproblem!(model::OSQPModel, A, l, u, c, lb, ub, sense)
(any(x -> x != -Inf, l) || any(x -> x != Inf, u)) && variablebounderror()
m, n = size(A)
resize!(model, n, m)

if sense == :Min
copy!(model.q, c)
elseif sense == :Max
model.q .= .-c
else
error("Objective sense not recognized")
end

copy!(model.l, lb)
copy!(model.u, ub)
copy!(model.A, A)

model
end

# TODO: writeproblem
MathProgBase.getvarLB(model::OSQPModel) = fill(-Inf, numvar(model))
MathProgBase.setvarLB!(model::OSQPModel, l) = variablebounderror()
MathProgBase.getvarUB(model::OSQPModel) = fill(Inf, numvar(model))
MathProgBase.setvarUB!(model::OSQPModel, l) = variablebounderror()
MathProgBase.getconstrLB(model::OSQPModel) = model.l
MathProgBase.setconstrLB!(model::OSQPModel, lb) = (copy!(model.l, lb); model)
MathProgBase.getconstrUB(model::OSQPModel) = model.u
MathProgBase.setconstrUB!(model::OSQPModel, ub) = (copy!(model.u, ub); model)
MathProgBase.setobj!(model::OSQPModel, c) = (copy!(model.q, c); model)
MathProgBase.getconstrmatrix(model::OSQPModel) = model.A
# TODO: addvar!
# TODO: delvars!
# TODO: addconstr!
# TODO: delconstrs!
# TODO: changecoeffs!
MathProgBase.numlinconstr(model::OSQPModel) = numconstr(model)
MathProgBase.getconstrsolution(model::OSQPModel) = model.A * getsolution(model)
# TODO: getreducedcosts
MathProgBase.getconstrduals(model::OSQPModel) = (checksolved(model); model.results.y)
# TODO: getinfeasibilityray
# TODO: getbasis
# TODO: getunboundedray
# TODO: getsimplexiter
# TODO: getbarrieriter


# http://mathprogbasejl.readthedocs.io/en/latest/lpqcqp.html#quadratic-programming
MathProgBase.numquadconstr(model::OSQPModel) = 0
MathProgBase.setquadobj!(model::OSQPModel, Q) = (copy!(model.P, Q); model)

function MathProgBase.setquadobj!(model::OSQPModel, rowidx, colidx, quadval)
nterms = length(quadval)
@boundscheck length(rowidx) == nterms || error()
@boundscheck length(colidx) == nterms || error()

# zero out coeffs
for i = 1 : nterms
@inbounds row = rowidx[i]
@inbounds col = colidx[i]
model.P[row, col] = 0
end

# add new coeffs
for i = 1 : nterms
@inbounds row = rowidx[i]
@inbounds col = colidx[i]
@inbounds val = quadval[i]
model.P[row, col] += val
model.P[col, row] = model.P[row, col]
end

model
end

# Note: skipping quadconstr methods

end # module