Skip to content

Commit c523cfb

Browse files
committed
Enable QCPDual by default
1 parent 32831bc commit c523cfb

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
@@ -481,3 +481,52 @@ end
481481
MOI.set(model, MOI.RawParameter("OutputFlag"), 0)
482482
@test MOI.get(model, MOI.RawParameter("OutputFlag")) == 0
483483
end
484+
485+
@testset "QCPDuals without needing to pass QCPDual=1" begin
486+
@testset "QCPDual default" begin
487+
model = Gurobi.Optimizer(GUROBI_ENV, OutputFlag=0)
488+
MOI.Utilities.loadfromstring!(model, """
489+
variables: x, y, z
490+
minobjective: 1.0 * x + 1.0 * y + 1.0 * z
491+
c1: x + y == 2.0
492+
c2: x + y + z >= 0.0
493+
c3: 1.0 * x * x + -1.0 * y * y + -1.0 * z * z >= 0.0
494+
c4: x >= 0.0
495+
c5: y >= 0.0
496+
c6: z >= 0.0
497+
""")
498+
MOI.optimize!(model)
499+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
500+
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
501+
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
502+
c1 = MOI.get(model, MOI.ConstraintIndex, "c1")
503+
c2 = MOI.get(model, MOI.ConstraintIndex, "c2")
504+
c3 = MOI.get(model, MOI.ConstraintIndex, "c3")
505+
@test MOI.get(model, MOI.ConstraintDual(), c1) 1.0 atol=1e-6
506+
@test MOI.get(model, MOI.ConstraintDual(), c2) 0.0 atol=1e-6
507+
@test MOI.get(model, MOI.ConstraintDual(), c3) 0.0 atol=1e-6
508+
end
509+
@testset "QCPDual=0" begin
510+
model = Gurobi.Optimizer(GUROBI_ENV, OutputFlag=0, QCPDual=0)
511+
MOI.Utilities.loadfromstring!(model, """
512+
variables: x, y, z
513+
minobjective: 1.0 * x + 1.0 * y + 1.0 * z
514+
c1: x + y == 2.0
515+
c2: x + y + z >= 0.0
516+
c3: 1.0 * x * x + -1.0 * y * y + -1.0 * z * z >= 0.0
517+
c4: x >= 0.0
518+
c5: y >= 0.0
519+
c6: z >= 0.0
520+
""")
521+
MOI.optimize!(model)
522+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
523+
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
524+
@test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION
525+
c1 = MOI.get(model, MOI.ConstraintIndex, "c1")
526+
c2 = MOI.get(model, MOI.ConstraintIndex, "c2")
527+
c3 = MOI.get(model, MOI.ConstraintIndex, "c3")
528+
@test_throws Gurobi.GurobiError MOI.get(model, MOI.ConstraintDual(), c1)
529+
@test_throws Gurobi.GurobiError MOI.get(model, MOI.ConstraintDual(), c2)
530+
@test_throws Gurobi.GurobiError MOI.get(model, MOI.ConstraintDual(), c3)
531+
end
532+
end

0 commit comments

Comments
 (0)