From b6259c02f2183aec24f081119f653bb361aece74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 18 Dec 2018 14:34:11 -0500 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=9A=9A=20MOIWrapper=20->=20MOI=5Fwrap?= =?UTF-8?q?per?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{MOIWrapper.jl => MOI_wrapper.jl} | 0 src/{MPBWrapper.jl => MPB_wrapper.jl} | 0 src/OSQP.jl | 4 ++-- test/{MOIWrapper.jl => MOI_wrapper.jl} | 0 test/{MPBWrapper.jl => MPB_wrapper.jl} | 0 test/runtests.jl | 4 ++-- 6 files changed, 4 insertions(+), 4 deletions(-) rename src/{MOIWrapper.jl => MOI_wrapper.jl} (100%) rename src/{MPBWrapper.jl => MPB_wrapper.jl} (100%) rename test/{MOIWrapper.jl => MOI_wrapper.jl} (100%) rename test/{MPBWrapper.jl => MPB_wrapper.jl} (100%) diff --git a/src/MOIWrapper.jl b/src/MOI_wrapper.jl similarity index 100% rename from src/MOIWrapper.jl rename to src/MOI_wrapper.jl diff --git a/src/MPBWrapper.jl b/src/MPB_wrapper.jl similarity index 100% rename from src/MPBWrapper.jl rename to src/MPB_wrapper.jl diff --git a/src/OSQP.jl b/src/OSQP.jl index 3c8fc5b..94aba41 100644 --- a/src/OSQP.jl +++ b/src/OSQP.jl @@ -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 diff --git a/test/MOIWrapper.jl b/test/MOI_wrapper.jl similarity index 100% rename from test/MOIWrapper.jl rename to test/MOI_wrapper.jl diff --git a/test/MPBWrapper.jl b/test/MPB_wrapper.jl similarity index 100% rename from test/MPBWrapper.jl rename to test/MPB_wrapper.jl diff --git a/test/runtests.jl b/test/runtests.jl index def9838..a66139a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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:") From a74a18c3dc537f5e64f286e8e836f8d7b108c9d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 18 Dec 2018 14:34:40 -0500 Subject: [PATCH 2/3] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20MOI=20v0.6=20->=20MOI?= =?UTF-8?q?=20v0.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- REQUIRE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REQUIRE b/REQUIRE index 4783c96..072334c 100644 --- a/REQUIRE +++ b/REQUIRE @@ -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 From d7c3d7090fc0253ea3281dcc49a3c219cc2b8192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 18 Dec 2018 15:06:42 -0500 Subject: [PATCH 3/3] Update to MOI v0.8 --- src/MOI_wrapper.jl | 68 ++++++++++++++++++++++----------------------- test/MOI_wrapper.jl | 44 +++++++++++++++-------------- 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index d0cb1a1..809286d 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -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}() @@ -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() @@ -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}() @@ -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}()) @@ -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) @@ -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.") @@ -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 diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index de1dbdd..b85948b 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -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] @@ -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 @@ -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)) @@ -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 @@ -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 @@ -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)) @@ -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 @@ -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 @@ -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() @@ -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) @@ -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