Skip to content

Commit

Permalink
Allow querying conflicts from within JuMP (#2300)
Browse files Browse the repository at this point in the history
* Add `compute_conflict!`, which calls the same function at the MOI level.

Also with a simple test for the only error case.

MOI dependency bumped to 0.9.14, as it's the first version supporting conflicts.

* Add docs for conflicts.

* Only show doc for JuMP.optimize!, not MOI.

Also remove it from solvers.md to avoid a warning when generating docs.

* Typo in optimize! doc.

* More desperate tentative to make docs work.

* Fix doctests

* >Wording changes suggested by @mlubin.

* Add an example for conflicts.

* Update solutions.md

* Improve docs.

* More explicit doc.

Co-authored-by: odow <[email protected]>
Co-authored-by: Miles Lubin <[email protected]>
  • Loading branch information
3 people authored Sep 7, 2020
1 parent 6c6e4b6 commit 4f8aaf0
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Calculus = "0.5"
DataStructures = "0.17, 0.18"
ForwardDiff = "~0.5.0, ~0.6, ~0.7, ~0.8, ~0.9, ~0.10"
JSON = "0.21"
MathOptInterface = "~0.9.11"
MathOptInterface = "~0.9.14"
MutableArithmetics = "0.2"
NaNMath = "0.3"
julia = "1"
Expand Down
50 changes: 49 additions & 1 deletion docs/src/solutions.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ This function will return a `MOI.TerminationStatusCode` `enum`.
MOI.TerminationStatusCode
```

Additionally, we can receive a solver specific string explaning why the
Additionally, we can receive a solver specific string explaining why the
optimization stopped with [`raw_status`](@ref).

## Solution statuses
Expand Down Expand Up @@ -249,6 +249,45 @@ lp_objective_perturbation_range
lp_rhs_perturbation_range
```

## Conflicts

When the model you input is infeasible, some solvers can help you find the
cause of this infeasibility by offering a conflict, i.e., a subset of the
constraints that create this infeasibility. Depending on the solver,
this can also be called an IIS (irreducible inconsistent subsystem).

The function [`compute_conflict!`](@ref) is used to trigger the computation of
a conflict. Once this process is finished, the attribute
[`MOI.ConflictStatus`](@ref) returns a [`MOI.ConflictStatusCode`](@ref).

If there is a conflict, you can query from each constraint whether it
participates in the conflict or not using the attribute
[`MOI.ConstraintConflictStatus`](@ref), which returns a
[`MOI.ConflictParticipationStatusCode`](@ref).

For instance, this is how you can use this functionality:

```julia
using JuMP
model = Model() # You must use a solver that supports conflict refining/IIS computation, like CPLEX or Gurobi
@variable(model, x >= 0)
@constraint(model, c1, x >= 2)
@constraint(model, c2, x <= 1)
optimize!(model)

# termination_status(model) will likely be MOI.INFEASIBLE,
# depending on the solver

compute_conflict!(model)
if MOI.get(model, MOI.ConflictStatus()) != MOI.CONFLICT_FOUND
error("No conflict could be found for an infeasible model.")
end

# Both constraints should participate in the conflict.
MOI.get(model, MOI.ConstraintConflictStatus(), c1)
MOI.get(model, MOI.ConstraintConflictStatus(), c2)
```

## Multiple solutions

Some solvers support returning multiple solutions. You can check how many
Expand Down Expand Up @@ -295,3 +334,12 @@ JuMP.simplex_iterations
JuMP.barrier_iterations
JuMP.node_count
```

```@docs
JuMP.compute_conflict!
MOI.compute_conflict!
MOI.ConflictStatus
MOI.ConflictStatusCode
MOI.ConstraintConflictStatus
MOI.ConflictParticipationStatusCode
```
20 changes: 19 additions & 1 deletion src/optimizer_interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ Optimize the model. If an optimizer has not been set yet (see
Keyword arguments `kwargs` are passed to the `optimize_hook`. An error is
thrown if `optimize_hook` is `nothing` and keyword arguments are provided.
```
"""
function optimize!(model::Model,
# TODO: Remove the optimizer_factory and bridge_constraints
Expand Down Expand Up @@ -144,6 +143,25 @@ function optimize!(model::Model,
return
end

"""
compute_conflict!(model::Model)
Compute a conflict if the model is infeasible. If an optimizer has not
been set yet (see [`set_optimizer`](@ref)), a [`NoOptimizer`](@ref)
error is thrown.
The status of the conflict can be checked with the `MOI.ConflictStatus`
model attribute. Then, the status for each constraint can be queried with
the `MOI.ConstraintConflictStatus` attribute.
"""
function compute_conflict!(model::Model)
if mode(model) != DIRECT && MOIU.state(backend(model)) == MOIU.NO_OPTIMIZER
throw(NoOptimizer())
end
MOI.compute_conflict!(backend(model))
return
end

"""
result_count(model::Model)
Expand Down
9 changes: 9 additions & 0 deletions test/model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,15 @@ end
function test_copy_model_jump_auto()
copy_model_style_mode(true, MOIU.AUTOMATIC)
end

@testset "Conflict computation" begin
@testset "NoOptimizer()" begin
err = NoOptimizer()
model = Model()
@test_throws err compute_conflict!(model)
end
end

function test_copy_model_base_auto()
copy_model_style_mode(false, MOIU.AUTOMATIC)
end
Expand Down

0 comments on commit 4f8aaf0

Please sign in to comment.