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

Indicator constraints could not be deleted #538

Closed
DatName opened this issue Dec 9, 2023 · 1 comment · Fixed by #540
Closed

Indicator constraints could not be deleted #538

DatName opened this issue Dec 9, 2023 · 1 comment · Fixed by #540

Comments

@DatName
Copy link

DatName commented Dec 9, 2023

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
Copy link
Member

odow commented Dec 9, 2023

I assume this is because of #516

function MOI.delete(
model::Optimizer,
c::MOI.ConstraintIndex{<:MOI.VectorAffineFunction,<:MOI.Indicator},
)
MOI.throw_if_not_valid(model, c)
row = _info(model, c).row
ind = Ref{Cint}(row - 1)
ret = GRBdelgenconstrs(model, 1, ind)
_check_ret(model, ret)
delete!(model.indicator_constraint_info, c.value)
for info in values(model.indicator_constraint_info)
if info.row > row
info.row -= 1
end
end
_require_update(model)
return
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

2 participants