Skip to content

Commit

Permalink
Fix treatment of objective value for infeasibility certificates (rays) (
Browse files Browse the repository at this point in the history
#150)

* Modify behavior of primal/dual objval attribute

* Enable unbounded MOI tests

* Update Tulip example tests

* Fix dual objective value computation

* Fix dual objective value for max problems
  • Loading branch information
mtanneau authored Dec 27, 2024
1 parent f3df04d commit 7cc9c1f
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 17 deletions.
6 changes: 3 additions & 3 deletions examples/infeasible.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ function ex_infeasible(::Type{Tv};
TLP.set_parameter(m, String(k), val)
end

# Read problem from .mps file and solve
# Read problem from .mps file and solve
TLP.load_problem!(m, joinpath(INSTANCE_DIR, "lpex_inf.mps"))
TLP.optimize!(m)

# Check status
@test TLP.get_attribute(m, TLP.Status()) == TLP.Trm_PrimalInfeasible
z = TLP.get_attribute(m, TLP.ObjectiveValue())
@test z == Inf
@test m.solution.primal_status == TLP.Sln_Unknown
@test m.solution.dual_status == TLP.Sln_InfeasibilityCertificate
z = TLP.get_attribute(m, TLP.ObjectiveValue())
@test z == zero(Tv) # Primal infeasible --> solution is zero

# Check unbounded dual ray
y1 = m.solution.y_lower[1] - m.solution.y_upper[1]
Expand Down
14 changes: 10 additions & 4 deletions examples/unbounded.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,12 @@ function ex_unbounded(::Type{Tv};
TLP.set_parameter(m, String(k), val)
end

# Read problem from .mps file and solve
# Read problem from .mps file and solve
TLP.load_problem!(m, joinpath(INSTANCE_DIR, "lpex_ubd.mps"))
TLP.optimize!(m)

# Check status
@test TLP.get_attribute(m, TLP.Status()) == TLP.Trm_DualInfeasible
z = TLP.get_attribute(m, TLP.ObjectiveValue())
@test z == -Tv(Inf)
@test m.solution.primal_status == TLP.Sln_InfeasibilityCertificate
@test m.solution.dual_status == TLP.Sln_Unknown

Expand All @@ -46,8 +44,16 @@ function ex_unbounded(::Type{Tv};
@test x2 >= -atol
@test isapprox(Ax1, 0, atol=atol, rtol=rtol)
@test -x1 - x2 <= -atol


zp = TLP.get_attribute(m, TLP.ObjectiveValue())
@test zp == (-x1 - x2)

zd = TLP.get_attribute(m, TLP.DualObjectiveValue())
@test zd == zero(Tv)

end

if abspath(PROGRAM_FILE) == @__FILE__
ex_unbounded(Float64)
end
end
48 changes: 44 additions & 4 deletions src/Interfaces/tulip_julia_api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,19 @@ Query the `ObjectiveValue` attribute from `model`
function get_attribute(m::Model{T}, ::ObjectiveValue) where{T}
if isnothing(m.solution)
error("Model has no solution")
end

pst = m.solution.primal_status

if pst != Sln_Unknown
z = dot(m.solution.x, m.pbdata.obj)
# If solution is a ray, ignore constant objective term
is_ray = m.solution.is_primal_ray
z0 = !is_ray * m.pbdata.obj0
return (z + z0)
else
z = m.solution.z_primal
return m.pbdata.objsense ? z : -z
# No solution, return zero
return zero(T)
end
end

Expand All @@ -257,8 +267,38 @@ Query the `DualObjectiveValue` attribute from `model`
function get_attribute(m::Model{T}, ::DualObjectiveValue) where{T}
if isnothing(m.solution)
error("Model has no solution")
end

dst = m.solution.dual_status

if dst != Sln_Unknown
yl = m.solution.y_lower
yu = m.solution.y_upper
sl = m.solution.s_lower
su = m.solution.s_upper

bl = m.pbdata.lcon
bu = m.pbdata.ucon
xl = m.pbdata.lvar
xu = m.pbdata.uvar

z = (
dot(yl, Diagonal(isfinite.(bl)), bl)
- dot(yu, Diagonal(isfinite.(bu)), bu)
+ dot(sl, Diagonal(isfinite.(xl)), xl)
- dot(su, Diagonal(isfinite.(xu)), xu)
)

# If problem is maximization, we need to negate the dual value
# to comply with MOI duality convention
z = m.pbdata.objsense ? z : -z

# If solution is a ray, ignore constant objective term
is_ray = m.solution.is_dual_ray
z0 = !is_ray * m.pbdata.obj0
return (z + z0)
else
z = m.solution.z_dual
return m.pbdata.objsense ? z : -z
# No solution, return zero
return zero(T)
end
end
6 changes: 0 additions & 6 deletions test/Interfaces/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ const CONFIG = MOIT.Config(Float64, atol=1e-6, rtol=1e-6,
"test_objective_set_via_modify",
# requires get quadratic objective
"test_objective_get_ObjectiveFunction_ScalarAffineFunction",
# Tulip not compliant with MOI convention for primal/dual infeasible models
# See expected behavior at https://jump.dev/MathOptInterface.jl/dev/background/infeasibility_certificates/
"test_unbounded",
# Tulip is not compliant with the MOI.ListOfModelAttributesSet attribute
"_in_ListOfModelAttributesSet",
]
Expand Down Expand Up @@ -65,9 +62,6 @@ end
"test_objective_set_via_modify",
# requires get quadratic objective
"test_objective_get_ObjectiveFunction_ScalarAffineFunction",
# Tulip not compliant with MOI convention for primal/dual infeasible models
# See expected behavior at https://jump.dev/MathOptInterface.jl/dev/background/infeasibility_certificates/
"test_unbounded",
# Tulip is not compliant with the MOI.ListOfModelAttributesSet attribute
"_in_ListOfModelAttributesSet",
],
Expand Down

0 comments on commit 7cc9c1f

Please sign in to comment.