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

Allow querying conflicts from within JuMP #2300

Merged
merged 14 commits into from
Sep 7, 2020
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Calculus = "0.5"
DataStructures = "0.17"
ForwardDiff = "~0.5.0, ~0.6, ~0.7, ~0.8, ~0.9, ~0.10"
MathOptInterface = "~0.9.11"
MathOptInterface = "~0.9.14"
MutableArithmetics = "0.2"
NaNMath = "0.3"
julia = "1"
Expand Down
49 changes: 48 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,44 @@ 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.
dourouc05 marked this conversation as resolved.
Show resolved Hide resolved

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).

dourouc05 marked this conversation as resolved.
Show resolved Hide resolved
For instance, this is how you can use this functionality:

```julia
using JuMP
model = Model()
@variable(model, x >= 0)
@constraint(model, c1, x >= 2)
@constraint(model, c2, x <= 1)
optimize!(model)
dourouc05 marked this conversation as resolved.
Show resolved Hide resolved

# 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 +333,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 @@ -470,6 +470,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