Skip to content

Commit

Permalink
Merge pull request #37 from oxfordcontrol/bl/moi8
Browse files Browse the repository at this point in the history
Update to MOI v0.8
  • Loading branch information
blegat authored Dec 18, 2018
2 parents e4fc5b6 + d7c3d70 commit 8dd46b5
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 60 deletions.
2 changes: 1 addition & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ BinDeps
julia 0.6
Compat 0.47.0 # for @warn
MathProgBase 0.7 0.8
MathOptInterface 0.6 0.7
MathOptInterface 0.8 0.9
68 changes: 34 additions & 34 deletions src/MOIWrapper.jl → src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
results = OSQP.Results()
is_empty = true
settings = Dict{Symbol, Any}()
sense = MOI.MinSense
sense = MOI.MIN_SENSE
objconstant = 0.
constrconstant = Float64[]
modcache = ProblemModificationCache{Float64}()
Expand All @@ -88,6 +88,8 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
end
end

MOI.get(::Optimizer, ::MOI.SolverName) = "OSQP"

# used to smooth out transition of OSQP v0.4 -> v0.5, TODO: remove on OSQP v0.6
export OSQPOptimizer
function OSQPOptimizer()
Expand All @@ -103,7 +105,7 @@ function MOI.empty!(optimizer::Optimizer)
optimizer.hasresults = false
optimizer.results = OSQP.Results()
optimizer.is_empty = true
optimizer.sense = MOI.MinSense # model parameter, so needs to be reset
optimizer.sense = MOI.MIN_SENSE # model parameter, so needs to be reset
optimizer.objconstant = 0.
optimizer.constrconstant = Float64[]
optimizer.modcache = ProblemModificationCache{Float64}()
Expand Down Expand Up @@ -180,7 +182,7 @@ function processobjective(src::MOI.ModelLike, idxmap)
sense = MOI.get(src, MOI.ObjectiveSense())
n = MOI.get(src, MOI.NumberOfVariables())
q = zeros(n)
if sense != MOI.FeasibilitySense
if sense != MOI.FEASIBILITY_SENSE
function_type = MOI.get(src, MOI.ObjectiveFunctionType())
if function_type == MOI.SingleVariable
fsingle = MOI.get(src, MOI.ObjectiveFunction{MOI.SingleVariable}())
Expand All @@ -204,7 +206,7 @@ function processobjective(src::MOI.ModelLike, idxmap)
else
throw(MOI.UnsupportedAttribute(MOI.ObjectiveFunction{function_type}()))
end
sense == MOI.MaxSense && (Compat.rmul!(P, -1); Compat.rmul!(q, -1); c = -c)
sense == MOI.MAX_SENSE && (Compat.rmul!(P, -1); Compat.rmul!(q, -1); c = -c)
else
P = spzeros(n, n)
q = zeros(n)
Expand Down Expand Up @@ -491,7 +493,7 @@ end

function MOI.get(optimizer::Optimizer, a::MOI.ObjectiveValue)
rawobj = optimizer.results.info.obj_val + optimizer.objconstant
ifelse(optimizer.sense == MOI.MaxSense, -rawobj, rawobj)
ifelse(optimizer.sense == MOI.MAX_SENSE, -rawobj, rawobj)
end

error_not_solved() = error("Problem is unsolved.")
Expand All @@ -504,69 +506,67 @@ end
# Since these aren't explicitly returned by OSQP, I feel like it would be better to have a fallback method compute these:
function MOI.get(optimizer::Optimizer, a::MOI.SolveTime)
check_has_results(optimizer)
optimizer.results.info.run_time
return optimizer.results.info.run_time
end

function MOI.get(optimizer::Optimizer, a::MOI.TerminationStatus)
check_has_results(optimizer)
# Note that the :Dual_infeasible and :Primal_infeasible are mapped to MOI.Success
# because OSQP can return a proof of infeasibility. For the same reason,
# :Primal_infeasible_inaccurate is mapped to MOI.AlmostSuccess
hasresults(optimizer) || return MOI.OPTIMIZE_NOT_CALLED
osqpstatus = optimizer.results.info.status
if osqpstatus == :Unsolved
error_not_solved() # TODO: good idea?
return MOI.OPTIMIZE_NOT_CALLED
elseif osqpstatus == :Interrupted
MOI.Interrupted
return MOI.INTERRUPTED
elseif osqpstatus == :Dual_infeasible
MOI.Success
return MOI.DUAL_INFEASIBLE
elseif osqpstatus == :Primal_infeasible
MOI.Success
return MOI.INFEASIBLE
elseif osqpstatus == :Max_iter_reached
MOI.IterationLimit
return MOI.ITERATION_LIMIT
elseif osqpstatus == :Solved
MOI.Success
return MOI.OPTIMAL
elseif osqpstatus == :Solved_inaccurate
MOI.AlmostSuccess
return MOI.ALMOST_OPTIMAL
elseif osqpstatus == :Primal_infeasible_inaccurate
MOI.AlmostSuccess
elseif osqpstatus == :Non_convex
MOI.InvalidModel
return MOI.ALMOST_INFEASIBLE
else
@assert osqpstatus == :Non_convex
return MOI.INVALID_MODEL
end
end

function MOI.get(optimizer::Optimizer, a::MOI.PrimalStatus)
hasresults(optimizer) || return MOI.NoSolution
hasresults(optimizer) || return MOI.NO_SOLUTION
osqpstatus = optimizer.results.info.status
if osqpstatus == :Unsolved
error("Problem is unsolved.") # TODO: good idea?
return MOI.NO_SOLUTION
elseif osqpstatus == :Primal_infeasible
MOI.InfeasibilityCertificate
return MOI.INFEASIBILITY_CERTIFICATE
elseif osqpstatus == :Solved
MOI.FeasiblePoint
return MOI.FEASIBLE_POINT
elseif osqpstatus == :Primal_infeasible_inaccurate
MOI.NearlyInfeasibilityCertificate
return MOI.NEARLY_INFEASIBILITY_CERTIFICATE
elseif osqpstatus == :Dual_infeasible
MOI.InfeasibilityCertificate
return MOI.INFEASIBILITY_CERTIFICATE
else # :Interrupted, :Max_iter_reached, :Solved_inaccurate, :Non_convex (TODO: good idea? use OSQP.SOLUTION_PRESENT?)
MOI.NoSolution
return MOI.NO_SOLUTION
end
end

function MOI.get(optimizer::Optimizer, a::MOI.DualStatus)
hasresults(optimizer) || return MOI.NoSolution
hasresults(optimizer) || return MOI.NO_SOLUTION
osqpstatus = optimizer.results.info.status
if osqpstatus == :Unsolved
error("Problem is unsolved.") # TODO: good idea?
return MOI.NO_SOLUTION
elseif osqpstatus == :Dual_infeasible
MOI.InfeasibilityCertificate
return MOI.INFEASIBILITY_CERTIFICATE
elseif osqpstatus == :Primal_infeasible
MOI.InfeasibilityCertificate
return MOI.INFEASIBILITY_CERTIFICATE
elseif osqpstatus == :Primal_infeasible_inaccurate
MOI.AlmostInfeasibilityCertificate
return MOI.NEARLY_INFEASIBILITY_CERTIFICATE
elseif osqpstatus == :Solved
MOI.FeasiblePoint
return MOI.FEASIBLE_POINT
else # :Interrupted, :Max_iter_reached, :Solved_inaccurate, :Non_convex (TODO: good idea? use OSQP.SOLUTION_PRESENT?)
MOI.NoSolution
return MOI.NO_SOLUTION
end
end

Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions src/OSQP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ end
include("constants.jl")
include("types.jl")
include("interface.jl")
include("MPBWrapper.jl")
include("MOIWrapper.jl")
include("MPB_wrapper.jl")
include("MOI_wrapper.jl")
const Optimizer = MathOptInterfaceOSQP.Optimizer

end # module
44 changes: 23 additions & 21 deletions test/MOIWrapper.jl → test/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ function test_optimizer_modification(modfun::Base.Callable, model::MOI.ModelLike
@test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.get(cleanoptimizer, MOI.PrimalStatus())
@test MOI.get(optimizer, MOI.ObjectiveValue()) MOI.get(cleanoptimizer, MOI.ObjectiveValue()) atol=atol rtol=rtol

if MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FeasiblePoint
if MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
modelvars = MOI.get(model, MOI.ListOfVariableIndices())
for v_model in modelvars
v_optimizer = idxmap[v_model]
Expand All @@ -163,7 +163,7 @@ function test_optimizer_modification(modfun::Base.Callable, model::MOI.ModelLike

if config.duals
@test MOI.get(optimizer, MOI.DualStatus()) == MOI.get(cleanoptimizer, MOI.DualStatus())
if MOI.get(optimizer, MOI.DualStatus()) == MOI.FeasiblePoint
if MOI.get(optimizer, MOI.DualStatus()) == MOI.FEASIBLE_POINT
for (F, S) in MOI.get(model, MOI.ListOfConstraints())
cis_model = MOI.get(model, MOI.ListOfConstraintIndices{F, S}())
for ci_model in cis_model
Expand Down Expand Up @@ -202,11 +202,11 @@ term(c, x::MOI.VariableIndex, y::MOI.VariableIndex) = MOI.ScalarQuadraticTerm(c,
vc2 = MOI.add_constraint(model, v[2], MOI.Interval(0.0, Inf))
objf = MOI.ScalarAffineFunction([term.([0.0, 0.0], v); term.([-1.0, 0.0], v); term.([0.0, 0.0], v)], 0.0)
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), objf)
MOI.set(model, MOI.ObjectiveSense(), MOI.MinSense)
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

optimizer = defaultoptimizer()
idxmap = MOI.copy_to(optimizer, model)
@test MOI.get(optimizer, MOI.ObjectiveSense()) == MOI.MinSense
@test MOI.get(optimizer, MOI.ObjectiveSense()) == MOI.MIN_SENSE
@test MOI.get(optimizer, MOI.NumberOfVariables()) == 2
@test MOI.get(optimizer, MOI.ListOfVariableIndices()) == [MOI.VariableIndex(1), MOI.VariableIndex(2)]
@test MOI.is_valid(optimizer, MOI.VariableIndex(2))
Expand All @@ -217,11 +217,11 @@ term(c, x::MOI.VariableIndex, y::MOI.VariableIndex) = MOI.ScalarQuadraticTerm(c,
# ensure that unmodified model is correct
atol = config.atol
rtol = config.rtol
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.Success
@test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FeasiblePoint
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
@test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
@test MOI.get(optimizer, MOI.ObjectiveValue()) -1 atol=atol rtol=rtol
@test MOI.get(optimizer, MOI.VariablePrimal(), getindex.(Ref(idxmap), v)) [1, 0] atol=atol rtol=rtol
@test MOI.get(optimizer, MOI.DualStatus()) == MOI.FeasiblePoint
@test MOI.get(optimizer, MOI.DualStatus()) == MOI.FEASIBLE_POINT
@test MOI.get(optimizer, MOI.ConstraintDual(), idxmap[c]) -1 atol=atol rtol=rtol
@test MOI.get(optimizer, MOI.ConstraintDual(), idxmap[vc1]) 0 atol=atol rtol=rtol
@test MOI.get(optimizer, MOI.ConstraintDual(), idxmap[vc2]) 1 atol=atol rtol=rtol
Expand Down Expand Up @@ -307,11 +307,11 @@ term(c, x::MOI.VariableIndex, y::MOI.VariableIndex) = MOI.ScalarQuadraticTerm(c,
end

testflipped = function ()
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.Success
@test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FeasiblePoint
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
@test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
@test MOI.get(optimizer, MOI.ObjectiveValue()) -1 atol=atol rtol=rtol
@test MOI.get(optimizer, MOI.VariablePrimal(), getindex.(Ref(idxmap), v)) [-1, 0] atol=atol rtol=rtol
@test MOI.get(optimizer, MOI.DualStatus()) == MOI.FeasiblePoint
@test MOI.get(optimizer, MOI.DualStatus()) == MOI.FEASIBLE_POINT
@test MOI.get(optimizer, MOI.ConstraintDual(), idxmap[c]) 1 atol=atol rtol=rtol
@test MOI.get(optimizer, MOI.ConstraintDual(), idxmap[vc1]) 0 atol=atol rtol=rtol
@test MOI.get(optimizer, MOI.ConstraintDual(), idxmap[vc2]) -1 atol=atol rtol=rtol
Expand All @@ -337,13 +337,13 @@ end
I, J, coeffs = findnz(A)
objf = MOI.ScalarQuadraticFunction(term.(q, x), [term(2 * P11, x[1], x[1])], 0.0)
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), objf)
MOI.set(model, MOI.ObjectiveSense(), MOI.MinSense)
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
cf = MOI.VectorAffineFunction(MOI.VectorAffineTerm.(Int64.(I), term.(coeffs, map(j -> getindex(x, j), J))), -u)
c = MOI.add_constraint(model, cf, MOI.Nonpositives(length(u)))

optimizer = defaultoptimizer()
idxmap = MOI.copy_to(optimizer, model)
@test MOI.get(optimizer, MOI.ObjectiveSense()) == MOI.MinSense
@test MOI.get(optimizer, MOI.ObjectiveSense()) == MOI.MIN_SENSE
@test MOI.get(optimizer, MOI.NumberOfVariables()) == 2
@test MOI.get(optimizer, MOI.ListOfVariableIndices()) == [MOI.VariableIndex(1), MOI.VariableIndex(2)]
@test MOI.is_valid(optimizer, MOI.VariableIndex(2))
Expand All @@ -354,11 +354,11 @@ end
# check result before modification
atol = config.atol
rtol = config.rtol
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.Success
@test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FeasiblePoint
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
@test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
@test MOI.get(optimizer, MOI.ObjectiveValue()) 20. atol=atol rtol=rtol
@test MOI.get(optimizer, MOI.VariablePrimal(), getindex.(Ref(idxmap), x)) [0.; 5.] atol=atol rtol=rtol
@test MOI.get(optimizer, MOI.DualStatus()) == MOI.FeasiblePoint
@test MOI.get(optimizer, MOI.DualStatus()) == MOI.FEASIBLE_POINT
@test MOI.get(optimizer, MOI.ConstraintDual(), idxmap[c]) -[1.666666666666; 0.; 1.3333333; 0.; 0.] atol=atol rtol=rtol

# test allocations
Expand Down Expand Up @@ -417,8 +417,8 @@ end
end

check_results = function (optimizer, idxmap, x, A, b, expected)
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.Success
@test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FeasiblePoint
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
@test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
@test MOI.get.(Ref(optimizer), Ref(MOI.VariablePrimal()), getindex.(Ref(idxmap), x)) expected atol = 1e-4
@test MOI.get(optimizer, MOI.ObjectiveValue()) norm(A * expected - b)^2 atol = 1e-4
end
Expand All @@ -431,7 +431,7 @@ end
model = OSQPModel{Float64}()
x = MOI.add_variables(model, n)
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), make_objective(P, q, r, x))
MOI.set(model, MOI.ObjectiveSense(), MOI.MinSense)
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
c = MOI.add_constraint(model, make_constraint_fun(C, d, x), MOI.Zeros(length(d)))

optimizer = defaultoptimizer()
Expand All @@ -458,11 +458,13 @@ end
@test inner.workspace == C_NULL
end

@test MOI.get(optimizer, MOI.SolverName()) == "OSQP"

model = OSQPModel{Float64}()
MOI.empty!(model)
x = MOI.add_variable(model)
c = MOI.add_constraint(model, x, MOI.GreaterThan(2.0))
MOI.set(model, MOI.ObjectiveSense(), MOI.MinSense)
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
MOI.set(model, MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(x))

MOI.copy_to(optimizer, model)
Expand All @@ -471,8 +473,8 @@ end
end

# TODO: consider moving to MOIT. However, current default copy_to is fine with BadObjectiveModel.
struct BadObjectiveModel <: MOIT.BadModel end # objective sense is not FeasibilitySense, but can't get objective function
MOI.get(src::BadObjectiveModel, ::MOI.ObjectiveSense) = MOI.MinSense
struct BadObjectiveModel <: MOIT.BadModel end # objective sense is not FEASIBILITY_SENSE, but can't get objective function
MOI.get(src::BadObjectiveModel, ::MOI.ObjectiveSense) = MOI.MIN_SENSE
struct ExoticFunction <: MOI.AbstractScalarFunction end
MOI.get(src::BadObjectiveModel, ::MOI.ObjectiveFunctionType) = ExoticFunction

Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ tests = [
"unconstrained.jl",
"warm_start.jl",
"update_matrices.jl",
"MPBWrapper.jl",
"MOIWrapper.jl"
"MPB_wrapper.jl",
"MOI_wrapper.jl"
]

println("Running tests:")
Expand Down

0 comments on commit 8dd46b5

Please sign in to comment.