jump-dev / Gurobi.jl

A Julia interface to the Gurobi Optimizer
http://www.gurobi.com/
MIT License
221 stars 80 forks source link

Indicator constraints could not be deleted #538

Closed DatName closed 9 months ago

DatName commented 10 months ago

Hi.

I found this issue with deleting and adding indicator constraints. Consider:

using JuMP
using Gurobi

mdl = JuMP.Model()
set_optimizer(mdl, Gurobi.Optimizer)

b1 = @variable(mdl, binary = true)
x1 = @variable(mdl, lower_bound = -1.0, upper_bound = 1.0)
x2 = @variable(mdl, lower_bound = -1.0, upper_bound = 1.0)

c1 = @constraint(mdl,  b1 => {x1 + x2 ==  0.1})
c2 = @constraint(mdl, !b1 => {x1 + x2 == -0.1})

@objective(mdl, Max, x1 - x2)
optimize!(mdl)

This works as expected. Note that output of Gurobi shows that there are 2 general contrains in the problem:

Set parameter Username
Gurobi Optimizer version 10.0.0 build v10.0.0rc2 (linux64)

CPU model: 12th Gen Intel(R) Core(TM) i9-12900H, instruction set [SSE2|AVX|AVX2]
Thread count: 20 physical cores, 20 logical processors, using up to 20 threads

Optimize a model with 0 rows, 3 columns and 0 nonzeros
Model fingerprint: 0xa8016401
Model has 2 general constraints
Variable types: 2 continuous, 1 integer (1 binary)
Coefficient statistics:
  Matrix range     [0e+00, 0e+00]
  Objective range  [1e+00, 1e+00]
  Bounds range     [1e+00, 1e+00]
  RHS range        [0e+00, 0e+00]
  GenCon rhs range [1e-01, 1e-01]
  GenCon coe range [1e+00, 1e+00]
Presolve added 1 rows and 0 columns
Presolve time: 0.00s
Presolved: 1 rows, 3 columns, 3 nonzeros
Variable types: 2 continuous, 1 integer (1 binary)
Found heuristic solution: objective 1.9000000

Root relaxation: objective 2.000000e+00, 1 iterations, 0.00 seconds (0.00 work units)

    Nodes    |    Current Node    |     Objective Bounds      |     Work
 Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

     0     0    2.00000    0    1    1.90000    2.00000  5.26%     -    0s

Explored 1 nodes (1 simplex iterations) in 0.00 seconds (0.00 work units)
Thread count was 20 (of 20 available processors)

Solution count 1: 1.9 

Optimal solution found (tolerance 1.00e-04)
Best objective 1.900000000000e+00, best bound 1.900000000000e+00, gap 0.0000%

User-callback calls 373, time in user-callback 0.00 sec

Now, lets delete and add back the same constraints:

JuMP.delete(mdl, c1)
JuMP.delete(mdl, c2)

c1 = @constraint(mdl,  b1 => {x1 + x2 ==  0.1})
c2 = @constraint(mdl, !b1 => {x1 + x2 == -0.1})

@objective(mdl, Max, x1 - x2)
optimize!(mdl)

now, Gurobi sees 3 general constraints:

Optimize a model with 0 rows, 3 columns and 0 nonzeros
Model fingerprint: 0xe65febfe
Model has 3 general constraints
...

If we repeat that last section of julia code, number of general constraints reported by Gurobi will constantly increase:

... Model has 4 general constraints ...
... Model has 5 general constraints ...

and so on. Though number of constraints reported by JuMP.all_constraints(mdl, include_variable_in_set_constraints=true) stays the same.


If we delete/add different constraints, there will be conflicts, though only after second delete

using JuMP
using Gurobi

mdl = JuMP.Model()
set_optimizer(mdl, Gurobi.Optimizer)

b1 = @variable(mdl, binary = true)
x1 = @variable(mdl, lower_bound = -1.0, upper_bound = 1.0)
x2 = @variable(mdl, lower_bound = -1.0, upper_bound = 1.0)

c1 = @constraint(mdl,  b1 => {x1 + x2 ==  0.1}) 
c2 = @constraint(mdl, !b1 => {x1 + x2 == -0.1})

@objective(mdl, Max, x1 - x2)
optimize!(mdl)
@assert JuMP.termination_status(mdl) == MOI.OPTIMAL

JuMP.delete(mdl, c1)
JuMP.delete(mdl, c2)

c1 = @constraint(mdl,  b1 => {x1 + x2 ==  0.2}) # ! changing right hand side
c2 = @constraint(mdl, !b1 => {x1 + x2 == -0.2}) # ! changing right hand side

@objective(mdl, Max, x1 - x2)
optimize!(mdl)
@assert JuMP.termination_status(mdl) == MOI.OPTIMAL

JuMP.delete(mdl, c1)
JuMP.delete(mdl, c2)

c1 = @constraint(mdl,  b1 => {x1 + x2 ==  0.1})
c2 = @constraint(mdl, !b1 => {x1 + x2 == -0.1})

@objective(mdl, Max, x1 - x2)
optimize!(mdl)
@assert JuMP.termination_status(mdl) == MOI.OPTIMAL # ! throws

currently, the last model is infeasible.

odow commented 10 months ago

I assume this is because of #516

https://github.com/jump-dev/Gurobi.jl/blob/68311c5f945f82d4cb5f073e31b1503cb6a7f92b/src/MOI_wrapper/MOI_indicator_constraint.jl#L194-L211