coin-or / python-mip

Python-MIP: collection of Python tools for the modeling and solution of Mixed-Integer Linear programs
Eclipse Public License 2.0
516 stars 90 forks source link

Resolving modified models with lazy constraints fails #216

Open christian2022 opened 2 years ago

christian2022 commented 2 years ago

When I build a model with a few lazy constraints, the first solve works fine. But after changing some elements and resolving, CBC reports infeasible, although after rebuilding the model from scratch it reports feasible. This does not happen if regular constraints instead of lazy constraints are used. Expection would be that it also works correctly with lazy constraints.


import mip, numpy as np

a = np.array([
    [1,0,0,1,1],
    [0,-1,0,0,0],
    [0,0,0,0,0],
    [0,-1,0,0,-1],
    [1,1,0,0,1],
    [1,1,1,0,1],
    [1,1,1,1,1],
    [2,2,2,1,1],
])
b = np.array([[-1,1], [0,1], [1,0], [1,1], [1,-1], [0,-1], [-1,0], [-1,-1]])
c = np.array([
    [0.5488135039273248, 0.6027633760716439, 0.4236547993389047, 0.4375872112626925, 0.9636627605010293],
    [0.7151893663724195, 0.5448831829968969, 0.6458941130666561, 0.8917730007820798, 0.3834415188257777],
])
d = np.array([
    [0.79172504, 0.52889492], 
    [0.46147936, 0.78052918], 
])

model = mip.Model()
λ = model.add_var_tensor((len(c[0]),), 'λ', lb=0, ub=1)
z = model.add_var_tensor((len(b[0]),), 'z', lb=0, var_type=mip.BINARY)
model.add_constr(mip.xsum(λ) == 1)
for expr in a @ λ + b @ z:
    model.add_lazy_constr(expr >= 0) # comment this line and uncomment next to get correct behaviour
    #model.add_constr(expr >= 0)
constr = [0] * len(d[0])
for i, expr in enumerate(c @ λ):
    constr[i] = model.add_constr(expr == d[0][i])
model.optimize()
print(f"Run model for d[0]: {model.status}") # reports optimal

# change model and resolve
for i, expr in enumerate(c @ λ):
    constr[i].rhs = d[1][i]
model.optimize()
print(f"Modified model for d[1]: {model.status}") # reports infeasible for lazy_constr

# rebuilt model for d[1] from scratch
model = mip.Model()
λ = model.add_var_tensor((len(c[0]),), 'λ', lb=0, ub=1)
z = model.add_var_tensor((len(b[0]),), 'z', lb=0, var_type=mip.BINARY)
model.add_constr(mip.xsum(λ) == 1)
for expr in a @ λ + b @ z:
    model.add_lazy_constr(expr >= 0)
for i, expr in enumerate(c @ λ):
    model.add_constr(expr == d[1][i])
model.optimize()
print(f"Rebuilt model for d[1]: {model.status}") # reports optimal
´´´

Python 3.8.12
Python-mip 1.13
CBC Mar 30 2021
sebheger commented 2 years ago

Hi @christian2022

Thanks for reporting the issue. I guess that the code is not modifying the lazy constraint as you expect. Instead, a constraint with the same internal index is modified. This could be related to commit from @h-g-s 531d71d97e0a0c8b69efbfe74df5ba48339dd163 at CBC. Will check what gurobi says to it and see how a fix could look like. Maybe at the moment manipulating lazy constraints after generation should be forbidden as it is not fully/correctly supported. There is no mechanism to distinguish if a constraint is a pure one or a lazy one.

sebheger commented 2 years ago

Ok, the same problem for gurobi