Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update IIS support to MOI 0.9.14. #299

Merged
merged 4 commits into from
Jul 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ MathProgBase = "fdba3010-5040-5b88-9595-932c9decdf73"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[compat]
Compat = "2"
MathOptInterface = "0.9.9"
Compat = "2, 3"
MathOptInterface = "0.9.14"
MathProgBase = "~0.5.0, ~0.6, ~0.7"
julia = "1"

Expand Down
133 changes: 56 additions & 77 deletions src/MOI/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2755,26 +2755,12 @@ function MOI.set(
return
end

"""
compute_conflict(model::Optimizer)

Compute a minimal subset of the constraints and variables that keep the model
infeasible.

See also `CPLEX.ConflictStatus` and `CPLEX.ConstraintConflictStatus`.
@deprecate compute_conflict MOI.compute_conflict!

Note that if `model` is modified after a call to `compute_conflict`, the
conflict is not purged, and any calls to the above attributes will return
values for the original conflict without a warning.
"""
function compute_conflict(model::Optimizer)
function MOI.compute_conflict!(model::Optimizer)
# In case there is no conflict, c_api_getconflict throws an error, while the
# conflict data structure can handle more gracefully this case (via a status
# check).

# TODO: decide what to do about the POSSIBLE statuses for the constraints
# (CPX_CONFLICT_POSSIBLE_MEMBER, CPX_CONFLICT_POSSIBLE_UB,
# CPX_CONFLICT_POSSIBLE_LB).
try
model.conflict = c_api_getconflict(model.inner)
catch exc
Expand Down Expand Up @@ -2802,57 +2788,38 @@ end
"""
ConflictStatus()

Return an `MOI.TerminationStatusCode` indicating the status of the last
computed conflict. If a minimal conflict is found, it will return
`MOI.OPTIMAL`. If the problem is feasible, it will return `MOI.INFEASIBLE`. If
`compute_conflict` has not been called yet, it will return
`MOI.OPTIMIZE_NOT_CALLED`.
Return the raw status from CPLEX indicating the status of the last
computed conflict. It returns an integer value defined in
[CPLEX' documentation](https://www.ibm.com/support/knowledgecenter/SSSA5P_12.10.0/ilog.odms.cplex.help/refcallablelibrary/macros/homepagesolutionstatus.html)
(with a name like `CPX_STAT_CONFLICT_*`) or `nothing` if the
function `compute_conflict!` has not yet been called.
"""
struct ConflictStatus <: MOI.AbstractModelAttribute end
struct ConflictStatus <: MOI.AbstractModelAttribute end

MOI.is_set_by_optimize(::ConflictStatus) = true
MOI.supports(::Optimizer, ::ConflictStatus) = true

function MOI.get(model::Optimizer, ::ConflictStatus)
if model.conflict === nothing
return MOI.OPTIMIZE_NOT_CALLED
elseif model.conflict.stat == CPX_STAT_CONFLICT_MINIMAL
return MOI.OPTIMAL
elseif model.conflict.stat == CPX_STAT_CONFLICT_FEASIBLE
return MOI.INFEASIBLE
elseif model.conflict.stat == CPX_STAT_CONFLICT_ABORT_CONTRADICTION
return MOI.OTHER_LIMIT
elseif model.conflict.stat == CPX_STAT_CONFLICT_ABORT_DETTIME_LIM
return MOI.TIME_LIMIT
elseif model.conflict.stat == CPX_STAT_CONFLICT_ABORT_IT_LIM
return MOI.ITERATION_LIMIT
elseif model.conflict.stat == CPX_STAT_CONFLICT_ABORT_MEM_LIM
return MOI.MEMORY_LIMIT
elseif model.conflict.stat == CPX_STAT_CONFLICT_ABORT_NODE_LIM
return MOI.NODE_LIMIT
elseif model.conflict.stat == CPX_STAT_CONFLICT_ABORT_OBJ_LIM
return MOI.OBJECTIVE_LIMIT
elseif model.conflict.stat == CPX_STAT_CONFLICT_ABORT_TIME_LIM
return MOI.TIME_LIMIT
elseif model.conflict.stat == CPX_STAT_CONFLICT_ABORT_USER
return MOI.OTHER_LIMIT
return nothing
else
return MOI.OTHER_LIMIT
return model.conflict.stat
end
end

function MOI.supports(::Optimizer, ::ConflictStatus)
return true
end

"""
ConstraintConflictStatus()
MOI.supports(::Optimizer, ::MOI.ConflictStatus) = true

A Boolean constraint attribute indicating whether the constraint participates
in the last computed conflict.
"""
struct ConstraintConflictStatus <: MOI.AbstractConstraintAttribute end

MOI.is_set_by_optimize(::ConstraintConflictStatus) = true
function MOI.get(model::Optimizer, ::MOI.ConflictStatus)
status = MOI.get(model, ConflictStatus())
if status === nothing
return MOI.COMPUTE_CONFLICT_NOT_CALLED
elseif status == CPX_STAT_CONFLICT_MINIMAL
return MOI.CONFLICT_FOUND
elseif status == CPX_STAT_CONFLICT_FEASIBLE
return MOI.NO_CONFLICT_EXISTS
else
return MOI.NO_CONFLICT_FOUND
end
end

function _get_conflict_status(
model::Optimizer,
Expand All @@ -2870,66 +2837,78 @@ end

function MOI.get(
model::Optimizer,
::ConstraintConflictStatus,
::MOI.ConstraintConflictStatus,
index::MOI.ConstraintIndex{MOI.SingleVariable, <:MOI.LessThan}
)
status = _get_conflict_status(model, index)
if status === nothing
return false
if status in [CPLEX.CPX_CONFLICT_MEMBER, CPLEX.CPX_CONFLICT_UB]
return MOI.IN_CONFLICT
elseif status in [CPLEX.CPX_CONFLICT_POSSIBLE_MEMBER, CPLEX.CPX_CONFLICT_POSSIBLE_UB]
return MOI.MAYBE_IN_CONFLICT
else
return MOI.NOT_IN_CONFLICT
end
return status == CPLEX.CPX_CONFLICT_MEMBER ||
status == CPLEX.CPX_CONFLICT_UB
end

function MOI.get(
model::Optimizer,
::ConstraintConflictStatus,
::MOI.ConstraintConflictStatus,
index::MOI.ConstraintIndex{MOI.SingleVariable, <:MOI.GreaterThan})
status = _get_conflict_status(model, index)
if status === nothing
return false
if status in [CPLEX.CPX_CONFLICT_MEMBER, CPLEX.CPX_CONFLICT_LB]
return MOI.IN_CONFLICT
elseif status in [CPLEX.CPX_CONFLICT_POSSIBLE_MEMBER, CPLEX.CPX_CONFLICT_POSSIBLE_LB]
return MOI.MAYBE_IN_CONFLICT
else
return MOI.NOT_IN_CONFLICT
end
return status == CPLEX.CPX_CONFLICT_MEMBER ||
status == CPLEX.CPX_CONFLICT_LB
end

function MOI.get(
model::Optimizer,
::ConstraintConflictStatus,
::MOI.ConstraintConflictStatus,
index::MOI.ConstraintIndex{MOI.SingleVariable, <:Union{MOI.EqualTo, MOI.Interval}}
)
status = _get_conflict_status(model, index)
if status === nothing
return false
if status in [CPLEX.CPX_CONFLICT_MEMBER, CPLEX.CPX_CONFLICT_LB, CPLEX.CPX_CONFLICT_UB]
return MOI.IN_CONFLICT
elseif status in [CPLEX.CPX_CONFLICT_POSSIBLE_MEMBER, CPLEX.CPX_CONFLICT_POSSIBLE_LB, CPLEX.CPX_CONFLICT_POSSIBLE_UB]
return MOI.MAYBE_IN_CONFLICT
else
return MOI.NOT_IN_CONFLICT
end
return status == CPLEX.CPX_CONFLICT_MEMBER ||
status == CPLEX.CPX_CONFLICT_LB ||
status == CPLEX.CPX_CONFLICT_UB
end

function MOI.get(
model::Optimizer,
::ConstraintConflictStatus,
::MOI.ConstraintConflictStatus,
index::MOI.ConstraintIndex{
<:MOI.ScalarAffineFunction,
<:Union{MOI.LessThan, MOI.GreaterThan, MOI.EqualTo}
}
)
_ensure_conflict_computed(model)
return (_info(model, index).row - 1) in model.conflict.rowind
rindex = findfirst(x -> x == _info(model, index).row - 1, model.conflict.rowind)
if rindex === nothing
return MOI.NOT_IN_CONFLICT
elseif model.conflict.rowstat[rindex] == CPX_CONFLICT_MEMBER
return MOI.IN_CONFLICT
else
return MOI.MAYBE_IN_CONFLICT
end
end

function MOI.supports(
::Optimizer,
::ConstraintConflictStatus,
::MOI.ConstraintConflictStatus,
::Type{MOI.ConstraintIndex{<:MOI.SingleVariable, <:SCALAR_SETS}}
)
return true
end

function MOI.supports(
::Optimizer,
::ConstraintConflictStatus,
::MOI.ConstraintConflictStatus,
::Type{MOI.ConstraintIndex{
<:MOI.ScalarAffineFunction,
<:Union{MOI.LessThan, MOI.GreaterThan, MOI.EqualTo}
Expand Down
98 changes: 55 additions & 43 deletions test/MathOptInterface/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -309,14 +309,16 @@ end
c2 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(1.0))

# Getting the results before the conflict refiner has been called must return an error.
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMIZE_NOT_CALLED
@test_throws ErrorException MOI.get(model, CPLEX.ConstraintConflictStatus(), c1)
@test MOI.get(model, CPLEX.ConflictStatus()) === nothing
@test MOI.get(model, MOI.ConflictStatus()) == MOI.COMPUTE_CONFLICT_NOT_CALLED
@test_throws ErrorException MOI.get(model, MOI.ConstraintConflictStatus(), c1)

# Once it's called, no problem.
CPLEX.compute_conflict(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMAL
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c1) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c2) == true
MOI.compute_conflict!(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == CPLEX.CPX_STAT_CONFLICT_MINIMAL
@test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND
@test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.IN_CONFLICT
end

@testset "Variable bounds (ScalarAffine)" begin
Expand All @@ -327,14 +329,16 @@ end
c2 = MOI.add_constraint(model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0], [x]), 0.0), MOI.LessThan(1.0))

# Getting the results before the conflict refiner has been called must return an error.
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMIZE_NOT_CALLED
@test_throws ErrorException MOI.get(model, CPLEX.ConstraintConflictStatus(), c1)
@test MOI.get(model, CPLEX.ConflictStatus()) === nothing
@test MOI.get(model, MOI.ConflictStatus()) == MOI.COMPUTE_CONFLICT_NOT_CALLED
@test_throws ErrorException MOI.get(model, MOI.ConstraintConflictStatus(), c1)

# Once it's called, no problem.
CPLEX.compute_conflict(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMAL
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c1) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c2) == true
MOI.compute_conflict!(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == CPLEX.CPX_STAT_CONFLICT_MINIMAL
@test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND
@test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.IN_CONFLICT
end

@testset "Two conflicting constraints (GreaterThan, LessThan)" begin
Expand All @@ -349,16 +353,18 @@ end
c2 = MOI.add_constraint(model, cf2, MOI.GreaterThan(1.0))

# Getting the results before the conflict refiner has been called must return an error.
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMIZE_NOT_CALLED
@test_throws ErrorException MOI.get(model, CPLEX.ConstraintConflictStatus(), c1)
@test MOI.get(model, CPLEX.ConflictStatus()) === nothing
@test MOI.get(model, MOI.ConflictStatus()) == MOI.COMPUTE_CONFLICT_NOT_CALLED
@test_throws ErrorException MOI.get(model, MOI.ConstraintConflictStatus(), c1)

# Once it's called, no problem.
CPLEX.compute_conflict(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMAL
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), b1) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), b2) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c1) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c2) == false
MOI.compute_conflict!(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == CPLEX.CPX_STAT_CONFLICT_MINIMAL
@test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND
@test MOI.get(model, MOI.ConstraintConflictStatus(), b1) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), b2) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.NOT_IN_CONFLICT
end

@testset "Two conflicting constraints (EqualTo)" begin
Expand All @@ -373,16 +379,18 @@ end
c2 = MOI.add_constraint(model, cf2, MOI.GreaterThan(1.0))

# Getting the results before the conflict refiner has been called must return an error.
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMIZE_NOT_CALLED
@test_throws ErrorException MOI.get(model, CPLEX.ConstraintConflictStatus(), c1)
@test MOI.get(model, CPLEX.ConflictStatus()) === nothing
@test MOI.get(model, MOI.ConflictStatus()) == MOI.COMPUTE_CONFLICT_NOT_CALLED
@test_throws ErrorException MOI.get(model, MOI.ConstraintConflictStatus(), c1)

# Once it's called, no problem.
CPLEX.compute_conflict(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMAL
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), b1) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), b2) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c1) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c2) == false
MOI.compute_conflict!(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == CPLEX.CPX_STAT_CONFLICT_MINIMAL
@test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND
@test MOI.get(model, MOI.ConstraintConflictStatus(), b1) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), b2) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.NOT_IN_CONFLICT
end

@testset "Variables outside conflict" begin
Expand All @@ -399,17 +407,19 @@ end
c2 = MOI.add_constraint(model, cf2, MOI.GreaterThan(1.0))

# Getting the results before the conflict refiner has been called must return an error.
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMIZE_NOT_CALLED
@test_throws ErrorException MOI.get(model, CPLEX.ConstraintConflictStatus(), c1)
@test MOI.get(model, CPLEX.ConflictStatus()) === nothing
@test MOI.get(model, MOI.ConflictStatus()) == MOI.COMPUTE_CONFLICT_NOT_CALLED
@test_throws ErrorException MOI.get(model, MOI.ConstraintConflictStatus(), c1)

# Once it's called, no problem.
CPLEX.compute_conflict(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMAL
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), b1) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), b2) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), b3) == false
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c1) == true
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c2) == false
MOI.compute_conflict!(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == CPLEX.CPX_STAT_CONFLICT_MINIMAL
@test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND
@test MOI.get(model, MOI.ConstraintConflictStatus(), b1) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), b2) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), b3) == MOI.NOT_IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.NOT_IN_CONFLICT
end

@testset "No conflict" begin
Expand All @@ -419,13 +429,15 @@ end
c2 = MOI.add_constraint(model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0], [x]), 0.0), MOI.LessThan(2.0))

# Getting the results before the conflict refiner has been called must return an error.
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.OPTIMIZE_NOT_CALLED
@test_throws ErrorException MOI.get(model, CPLEX.ConstraintConflictStatus(), c1)
@test MOI.get(model, CPLEX.ConflictStatus()) === nothing
@test MOI.get(model, MOI.ConflictStatus()) == MOI.COMPUTE_CONFLICT_NOT_CALLED
@test_throws ErrorException MOI.get(model, MOI.ConstraintConflictStatus(), c1)

# Once it's called, no problem.
CPLEX.compute_conflict(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == MOI.INFEASIBLE
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c1) == false
@test MOI.get(model, CPLEX.ConstraintConflictStatus(), c2) == false
MOI.compute_conflict!(model)
@test MOI.get(model, CPLEX.ConflictStatus()) == CPLEX.CPX_STAT_CONFLICT_FEASIBLE
@test MOI.get(model, MOI.ConflictStatus()) == MOI.NO_CONFLICT_EXISTS
@test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.NOT_IN_CONFLICT
@test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.NOT_IN_CONFLICT
end
end