Skip to content

Adding constraints through a callback function #994

@LuizSuzana

Description

@LuizSuzana

Hello everybody!

I'm using pyscipopt 5.4.1 and implemented a simple model with a callback function set through Model.attachEventHandlerCallback to add some constraints during the solving process for the event SOLFOUND. However, it seems that after the optimization has ended, the optimal solution found violates one of the constraints added inside the callback.

Here's a snippet of code to reproduce this behavior:

import pyscipopt as scip

# base model
mdl = scip.Model("example")
x = mdl.addVar("x", lb=0, ub=10, vtype='C')
y = mdl.addVar("y", lb=0, ub=10, vtype='C')
cons1 = mdl.addCons(x + y >= 1, name='C1')
mdl.setObjective(2 * x + y, sense='minimize')  # at first, the expected optimal solution is x=0, y=1

# define the callback function
count = 0
def callback_function(model, event):
    global count
    count += 1

    best_sol = model.getBestSol()
    print("Best objective value: ", model.getObjVal(best_sol))
    print(f"Solution: x={model.getSolVal(best_sol, x)}, y={model.getSolVal(best_sol, y)}")
    
    if count == 1:
        # first callback call
        model.addCons(x >= 2, name='LazyCons_x_1')
        model.addCons(x <= 5, name='LazyCons_x_2')
    elif count == 2:
        # second callback call
        model.addCons(y >= 1, name='LazyCons_y')
    
# add callback
mdl.attachEventHandlerCallback(callback_function, [scip.SCIP_EVENTTYPE.SOLFOUND])

# optimize
mdl.optimize()
print("x:", mdl.getVal(x))
print("y:", mdl.getVal(y))
print(f"Callback was used {count} times.")
mdl.writeProblem("trans_model.lp", trans=True)

Basically, without the callback, the expected optimal solution is x=0 and y=1. During the solving process, the first feasible solution found is x=10 and y=10. The callback is called (since a new feasible solution was found). Inside the callback on its first call, I add constraints to ensure 2 <= x <= 5. The optimal solution then correctly becomes x=2 and y=0, which is found by SCIP, and the callback is called again. Inside the callback, for its second call, I add y >= 1. This should make x=2 and y=1 the optimal solution. However, the solving process ends returning x=2 and y=0 as the optimal one, violating the last constraint added by the callback.

Below is the transformed model:

\ SCIP STATISTICS
\   Problem name     : t_example
\   Variables        : 2 (0 binary, 0 integer, 0 implicit integer, 2 continuous)
\   Constraints      : 1
Minimize
 Obj: +2 t_x +1 t_y
Subject to
 LazyCons_y: +1 t_y >= +1
Bounds
 2 <= t_x <= 2
 0 <= t_y <= 0
End

We see that the constraint t_y >= 1 is present in the transformed model. One thing that caught my attention is that in the Bounds section, we see 0 <= t_y <= 0, so maybe the fact that t_y >= 1 contradicts those bounds makes it be ignored.

Is this behavior expected? Am I using the wrong framework for this goal (I found constraint handlers framework on the online documentation as well)? I also tried the same callback routine through a class-based approach using includeEventhdlr (following this docs) but found the same issue.

Thanks in advance for looking into this!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions