Skip to content

Commit f164589

Browse files
committed
Enable QCPDual by default
1 parent f62f9d5 commit f164589

File tree

2 files changed

+57
-1
lines changed

2 files changed

+57
-1
lines changed

src/MOI_wrapper.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ mutable struct Optimizer <: MOI.ModelLike
106106
107107
Note that we set the parameter `InfUnbdInfo` to `1` rather than the default
108108
of `0` so that we can query infeasibility certificates. Users are, however,
109-
free to overide this as follows `Optimizer(InfUndbInfo=0)`.
109+
free to over-ride this as follows `Optimizer(InfUndbInfo=0)`. In addition,
110+
we also set `QCPDual` to `1` to enable duals in QCPs. Users can override
111+
this by passing `Optimizer(QCPDual=0)`.
110112
"""
111113
function Optimizer(env::Union{Nothing, Env} = nothing; kwargs...)
112114
model = new()
@@ -127,6 +129,9 @@ mutable struct Optimizer <: MOI.ModelLike
127129
if !haskey(model.params, "InfUnbdInfo")
128130
MOI.set(model, MOI.RawParameter("InfUnbdInfo"), 1)
129131
end
132+
if !haskey(model.params, "QCPDual")
133+
MOI.set(model, MOI.RawParameter("QCPDual"), 1)
134+
end
130135
return model
131136
end
132137
end
@@ -1748,6 +1753,8 @@ function MOI.get(model::Optimizer, ::MOI.DualStatus)
17481753
stat = get_status(model.inner)
17491754
if is_mip(model.inner)
17501755
return MOI.NO_SOLUTION
1756+
elseif is_qcp(model.inner) && MOI.get(model, MOI.RawParameter("QCPDual")) != 1
1757+
return MOI.NO_SOLUTION
17511758
elseif stat == :optimal
17521759
return MOI.FEASIBLE_POINT
17531760
elseif stat == :solution_limit

test/MOI_wrapper.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,3 +318,52 @@ end
318318
MOI.set(model, MOI.RawParameter("OutputFlag"), 0)
319319
@test MOI.get(model, MOI.RawParameter("OutputFlag")) == 0
320320
end
321+
322+
@testset "QCPDuals without needing to pass QCPDual=1" begin
323+
@testset "QCPDual default" begin
324+
model = Gurobi.Optimizer(GUROBI_ENV, OutputFlag=0)
325+
MOI.Utilities.loadfromstring!(model, """
326+
variables: x, y, z
327+
minobjective: 1.0 * x + 1.0 * y + 1.0 * z
328+
c1: x + y == 2.0
329+
c2: x + y + z >= 0.0
330+
c3: 1.0 * x * x + -1.0 * y * y + -1.0 * z * z >= 0.0
331+
c4: x >= 0.0
332+
c5: y >= 0.0
333+
c6: z >= 0.0
334+
""")
335+
MOI.optimize!(model)
336+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
337+
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
338+
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
339+
c1 = MOI.get(model, MOI.ConstraintIndex, "c1")
340+
c2 = MOI.get(model, MOI.ConstraintIndex, "c2")
341+
c3 = MOI.get(model, MOI.ConstraintIndex, "c3")
342+
@test MOI.get(model, MOI.ConstraintDual(), c1) 1.0 atol=1e-6
343+
@test MOI.get(model, MOI.ConstraintDual(), c2) 0.0 atol=1e-6
344+
@test MOI.get(model, MOI.ConstraintDual(), c3) 0.0 atol=1e-6
345+
end
346+
@testset "QCPDual=0" begin
347+
model = Gurobi.Optimizer(GUROBI_ENV, OutputFlag=0, QCPDual=0)
348+
MOI.Utilities.loadfromstring!(model, """
349+
variables: x, y, z
350+
minobjective: 1.0 * x + 1.0 * y + 1.0 * z
351+
c1: x + y == 2.0
352+
c2: x + y + z >= 0.0
353+
c3: 1.0 * x * x + -1.0 * y * y + -1.0 * z * z >= 0.0
354+
c4: x >= 0.0
355+
c5: y >= 0.0
356+
c6: z >= 0.0
357+
""")
358+
MOI.optimize!(model)
359+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
360+
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
361+
@test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION
362+
c1 = MOI.get(model, MOI.ConstraintIndex, "c1")
363+
c2 = MOI.get(model, MOI.ConstraintIndex, "c2")
364+
c3 = MOI.get(model, MOI.ConstraintIndex, "c3")
365+
@test_throws Gurobi.GurobiError MOI.get(model, MOI.ConstraintDual(), c1)
366+
@test_throws Gurobi.GurobiError MOI.get(model, MOI.ConstraintDual(), c2)
367+
@test_throws Gurobi.GurobiError MOI.get(model, MOI.ConstraintDual(), c3)
368+
end
369+
end

0 commit comments

Comments
 (0)