Skip to main content

How to add lazy constraint using callback?

Answered

Comments

24 comments

  • HAOXING OUYANG
    Conversationalist
    First Question
    Set parameter Threads to value 12
    Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (win64 - Windows 11+.0 (22631.2))

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

    Set parameter Method to value 1
    Set parameter MIPFocus to value 1
    Set parameter TimeLimit to value 3600
    Set parameter LazyConstraints to value 1
    Warning: linear constraint 0 and linear constraint 60 have the same name "internalVar_sumZ_0_0"
    Warning: quadratic constraint 4020 and quadratic constraint 4021 have the same name "cos_14"
    Warning: general constraint 0 and general constraint 1 have the same name "constr_aVars[i][k][0]"
    Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (win64 - Windows 11+.0 (22631.2))

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

    Optimize a model with 2413 rows, 7676 columns and 22245 nonzeros
    Model fingerprint: 0xd2211c9e
    Model has 4135 quadratic constraints
    Model has 600 general constraints
    Variable types: 2388 continuous, 5288 integer (1195 binary)
    Coefficient statistics:
    Matrix range [5e-01, 3e+04]
    QMatrix range [1e-01, 1e+00]
    QLMatrix range [1e-01, 1e+03]
    Objective range [1e+00, 9e+00]
    Bounds range [1e+00, 3e+04]
    RHS range [5e-01, 1e+03]
    QRHS range [1e-04, 3e+04]
    Presolve added 35 rows and 0 columns
    Presolve removed 0 rows and 1306 columns
    Presolve time: 0.09s
    Presolved: 21743 rows, 16045 columns, 76535 nonzeros
    Presolved model has 4430 SOS constraint(s)
    Presolved model has 1980 bilinear constraint(s)

    Solving non-convex MIQCP

    Variable types: 6463 continuous, 9582 integer (3765 binary)

    Root relaxation: objective 3.685120e+01, 16506 iterations, 0.83 seconds (1.06 work units)

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

    0 0 36.85120 0 4925 - 36.85120 - - 1s
    0 0 36.85120 0 4988 - 36.85120 - - 2s
    0 0 36.85120 0 5186 - 36.85120 - - 2s
    0 0 36.85120 0 5451 - 36.85120 - - 3s
    0 0 36.85120 0 5249 - 36.85120 - - 3s
    0 0 36.85120 0 5327 - 36.85120 - - 3s
    0 0 36.85120 0 5340 - 36.85120 - - 4s
    0 0 36.85120 0 5310 - 36.85120 - - 4s
    0 0 36.85120 0 5301 - 36.85120 - - 4s
    0 0 36.85120 0 5350 - 36.85120 - - 5s
    0 0 36.85120 0 5308 - 36.85120 - - 5s
    0 0 36.85120 0 4887 - 36.85120 - - 6s
    0 0 36.85120 0 5007 - 36.85120 - - 7s
    0 0 36.85120 0 4935 - 36.85120 - - 8s
    0 0 36.85120 0 4893 - 36.85120 - - 8s

    ......

    37580 14925 44.02673 28 1782 45.20000 40.84175 9.64% 2094 3165s
    38819 15305 44.53873 28 1064 45.20000 40.88571 9.54% 2088 3248s
    40139 15738 41.14821 25 1402 45.20000 40.91276 9.49% 2078 3331s
    41568 16266 infeasible 30 45.20000 40.93994 9.42% 2069 3413s
    43067 16738 43.11354 28 848 45.20000 40.96087 9.38% 2057 3507s
    44419 17215 43.61052 29 875 45.20000 40.98026 9.34% 2046 3590s
    45836 17294 43.48187 25 1316 45.20000 40.99963 9.29% 2037 3600s

    Cutting planes:
    Gomory: 1
    Cover: 30
    Implied bound: 1086
    Clique: 26
    MIR: 204
    StrongCG: 9
    Flow cover: 495
    Flow path: 66
    Inf proof: 12
    Zero half: 8
    Network: 12
    RLT: 1873
    Relax-and-lift: 143
    BQP: 121
    PSD: 1

    Explored 46027 nodes (93786381 simplex iterations) in 3600.29 seconds (3180.89 work units)
    Thread count was 12 (of 12 available processors)

    Solution count 10: 45.2 45.924 45.984 ... 54.237

    Time limit reached
    Best objective 4.520000000000e+01, best bound 4.099963149378e+01, gap 9.2929%

    User-callback calls 229311, time in user-callback 1.43 sec

    This is my log, does this means that the callback has been successfully used?

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Haoxing,

    This line:

    User-callback calls 229311, time in user-callback 1.43 sec

    tells you that the callback method is being called, however if lazy constraints were added then we should see a "Lazy constraints" entry under "Cutting planes".

    Can you post your callback method?

    - Riley

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    Hi Riley,

    So glad to get your answered. Here's my callback method, I will use one of the lazy constraints as an example:

    package Model.Main;

    import Model.Instance;
    import com.gurobi.gurobi.*;

    public class LazyCallback extends GRBCallback {
    private GRBVar[][][] c;
    private GRBVar[][][] X;
    private Instance instance;

    public LazyCallback(GRBVar[][][] c, GRBVar[][][] X, Instance instance) {
    this.c = c;
    this.X = X;
    this.instance = instance;
    }

    @Override
    protected void callback() {
    try {
    if (where == GRB.CB_MIPNODE) {
    int nodeStatus = this.getIntInfo(GRB.CB_MIPNODE_STATUS);
    if (nodeStatus == GRB.OPTIMAL) {

    /**
    * Constraint:c[i][j][k] <= X[i][j][k]
    */
    for (int k = 0; k < instance.vehiclesNum; k++) {
    for (int i = 0; i < instance.nodesNum; i++) {
    for (int j = 0; j < instance.nodesNum; j++) {
    double cVal = getSolution(c[i][j][k]);
    double xVal = getSolution(X[i][j][k]);
    if (cVal > xVal) {
    GRBLinExpr lhs = new GRBLinExpr();
    lhs.addTerm(1.0, c[i][j][k]);
    lhs.addTerm(-1.0, X[i][j][k]);
    addLazy(lhs, GRB.LESS_EQUAL, 0);
    }
    }
    }
    }

    }
    }

    } catch (GRBException e) {
    System.out.println("Error code: " + e.getErrorCode() + ". " + e.getMessage());
    }
    }
    }

    After I build the callback method, I call it in the main model:

    LazyCallback callback = new LazyCallback(c, X, instance);
    model.setCallback(callback);
    model.optimize();

    May | ask if this operation correctly uses the callback method?

    Why does my solving speed not change after running the model within callback, sometimes even longer?

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Haoxing,

    Are you adding lazy constraints, or cutting planes?  I.e. are these necessary for validity of the solution, or just to hopefully improve performance of the solver?

    Note that in the docs for addLazy it says:

    You would typically add a lazy constraint by first querying the current node solution (by calling getSolution from a GRB.CB_MIPSOL callback, or getNodeRel from a GRB.CB_MIPNODE callback), and then calling addLazy() to add a constraint that cuts off the solution

    Can you try replacing getSolution with getNodeRel in your callback code?

    Do you have

    model.set(GRB.IntParam.LazyConstraints, 1);

    somewhere in your code?

    Can you try adding a public int to your callback class, and increase its value in the line after 

    addLazy(lhs, GRB.LESS_EQUAL, 0);

     then query its value after the model has solved, to see if this code is reached?

    - Riley

     

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    Hi Riley,

    Thank you for your reminder. It's true that the "where"setting in my callback class is incorrect. Since I am adding a lazy constraint, it should be set to where==GRB CB-MIPSOL.

    I ran the model again and displayed “Lazy Constraint” in “Cutting Planes”:

    Cutting planes:
      Gomory: 2
      Implied bound: 377
      MIR: 136
      StrongCG: 4
      Flow cover: 61
      Zero half: 15
      Network: 16
      RLT: 474
      Relax-and-lift: 20
      BQP: 79
      Lazy constraints: 2

    Now I am facing with a new problem is: solving the model directly is faster than using Lazy constraints.

    May I ask what the reason is?

     

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Haoxing,

    When you say solving the model directly is faster, do you mean adding all

    c[i][j][k] <= X[i][j][k]

    constraints into the model at the beginning, instead of using a callback?

    - Riley

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    Hi Riley,
    Yes, I mean solving the original model is faster than using callback method.

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Haoxing,

    Did you test this across many values of Seed, or just 1 run of each?

    - Riley

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    Hi Riley,

    After listening to your advice, I set:

    model.set(GRB.IntParam.Seed, 2000000000);

    I tested it with different examples, and in some cases, the solution speed using the callback method is indeed faster than sovling the original model!! But in some cases the speed of solving the original model is still faster.

    So I think:

    (1)The effectiveness of using the callback method still depends on the characteristics of the example, right?

    (2)Does Gurobi have an official document stating that using callback will accelerate the solving speed and convergence?

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Haoxing,

    in some cases, the solution speed using the callback method is indeed faster than sovling the original model!! But in some cases the speed of solving the original model is still faster.

    This is indeed the reason why it is important to compare a distribution of run times against another.  Our python package gurobi-logtools makes this easy to visualize with boxplots.

    The effectiveness of using the callback method still depends on the characteristics of the example, right?

    The effectiveness will depend on how expensive it is to calculate the lazy constraints, how often the callback is called, and how many lazy constraints result.  All of these can depend on the instance data.

    Does Gurobi have an official document stating that using callback will accelerate the solving speed and convergence?

    No, because it not necessarily true.  Consider a trivial example where every constraint in your model is added as a lazy constraint.  This is a terrible idea...  Maybe lazy constraints are a better approach, maybe they're not - you have to test.  Experimentation is crucial to model building.

    - Riley

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    Hi Riley,

    Thank you very much for your reply!

    Best,

    Haoxing

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Haoxing,

    Another thing I forgot to mention...  This:

    model.set(GRB.IntParam.LazyConstraints, 1);

    does not come for free.  To be able to add lazy constraints we need to turn off some presolve reductions, which in general will lead to a weaker presolve result, so there is an initial cost to pay even if you then don't add any lazy constraints during the optimization.

    -  Riley

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    Hi Riley,

    Thank you for your reminder! But I'm facing another problem now. Could you please help me take a look?

    What I want to solve is a Vehicle Routing Problem, using three-index variables X[i][j][k] to represent vehicle k travels from node i to j. As the model has three-index variables representing homogeneous routes of vehicles, so I want to reduce the solution symmetry with constraints(34)-(35) using "User Cut" by callback method, the deatils is in the picture:

    But after adding "User Cut", the model has no solution, and I don't know if the code in the "User Cut" class is incorrect:

    public class UserCut extends GRBCallback {
    private GRBVar[][][] X;
    private Vehicle vehicle;
    private Instance instance;

    public UserCut(GRBVar[][][] X, Vehicle vehicle, Instance instance) {
    this.X = X;
    this.vehicle = vehicle;
    this.instance = instance;
    }

    @Override
    protected void callback() {
    try {
    if (where == GRB.CB_MIPNODE) {
    int nodeStatus = this.getIntInfo(GRB.CB_MIPNODE_STATUS);
    if (nodeStatus == GRB.OPTIMAL) {
    //Constraint(34)
    for (int k = 1; k < instance.vehiclesNum; k++) {
    for (int i = 1; i < instance.nodesNum - 1; i++) {
    for (int j = 0; j < instance.nodesNum; j++) {
    if (j != i) {
    double lhs = getNodeRel(X[j][i][k]);
    for (int h = 1; h < instance.nodesNum - 1; h++) {
    double rhs = 0;
    if (h != j && h <= i) {
    rhs = getNodeRel(X[j][h][k - 1]);
    }
    if (rhs < lhs) {
    GRBLinExpr expr = new GRBLinExpr();
    expr.addTerm(1, X[j][i][k]);
    expr.addTerm(-1, X[j][h][k - 1]);
    addCut(expr, GRB.LESS_EQUAL, 0);
    }
    }
    }
    }
    }
    }
    }
    //Constraint(35)
    for (int j = 0; j < instance.nodesNum; j++) {
    if (j != 1) {
    double var = getNodeRel(X[j][1][0]);
    if (var != 1) {
    GRBLinExpr expr = new GRBLinExpr();
    expr.addTerm(1, X[j][1][0]);
    addCut(expr, GRB.EQUAL, 1);
    }
    }
    }

    }
    } catch (GRBException e) {
    System.out.println("Error code: " + e.getErrorCode() + ". " + e.getMessage());
    }
    }
    }

    I have already set:

    model.set(GRB.IntParam.PreCrush, 1);

    I really need your help.

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    I forgot to mention that in my code, "instance. nodesNum" includes node 0 and node n+1 (both of which are the same depot). This is a bit different from the constraints(34)-(35) in the picture.

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Haoxing,

    I think there's some typos, but I'd guess the main issue seems to be that the translation of math into code is way off.

    It should be something like this:

    for (int k = 1; k < instance.vehiclesNum; k++) {
        for (int i = 1; i < instance.nodesNum-1; i++) {
            double lhs = 0;
            for (int j = 0; j < instance.nodesNum-1; j++) {
                if (j != i) {
                    lhs += getNodeRel(X[j][i][k]);
                }
            }
            double rhs = 0;
            for (int j = 0; j < instance.nodesNum; j++) {
                if (j != i) {
                    for (int h = 1; h < instance.nodesNum-1; h++) {
                        if (h != j && h <= i) {
                            rhs += getNodeRel(X[j][h][k-1]);
                        }
                    }
                }
            }
            if (rhs < lhs) {
                // construct constraint
            }
        }
    }

    Note that this code is not tested, any maybe there are some tweaks to be made based off your different indices.  I've left the constraint construction as an exercise, but it needs to closely mirror the calculation of lhs and rhs.  There is probably an elegant way to combine them both.

    Also note that there is no guarantee that Gurobi will use a user-cut.  This is the difference between lazy constraints and user cuts.  Lazy constraints will always be used by Gurobi since they are needed for validity.  User cuts are not needed for validity and Gurobi does not have to add them if it thinks it is a bad idea.

    - Riley

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    Hi Riley,

    Thanks for your reply! I made modifications based on your code. After testing, the code for User Cut is feasible.

    Now what makes me confused is:

    When I use User Cut or Lazy Constraints alone, runing the model can obtain feasible solutions. But when I use User Cut and Lazy Constraints at the same time, model usually have no solution(in many cases). An example log information for a infeasible model using both User Cut and Lazy Constraints is as follows:

    Cutting planes:
      User: 1296
      Learned: 16
      Gomory: 6
      Cover: 6
      Implied bound: 697
      Clique: 46
      MIR: 196
      StrongCG: 2
      Flow cover: 5
      GUB cover: 1
      Inf proof: 6
      Zero half: 113
      Network: 1
      RLT: 1070
      Relax-and-lift: 82
      BQP: 1
      PSD: 1
      Lazy constraints: 24

    May I ask why this is happening?

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Haoxing,

    Let me see if I understand correctly.

    You have some constraints which can be used to break symmetry and are not necessary for a correct solution.

    When you add all of these as normal constraints you get a feasible solution.

    When you set LazyConstraints=1 and add these constraints with cbLazy in a MIPSOL callback you get a feasible solution.

    When you set PreCrush=1 and add these constraints with cbCut in a MIPNODE callback you get a feasible solution.

    When you set LazyConstraints=1, PreCrush=1 and add the constraints (potentially twice) using both cbLazy and cbCut (??) you get an infeasible model?

    Is this correct?

    - Riley

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    Hi Riley,

    There seems to be a slight deviation:

    (1)

    You have some constraints which can be used to break symmetry and are not necessary for a correct solution.

    When you add all of these as normal constraints you get a feasible solution.

    I used the "break symmetry" constraints as the User Cut, they are not included in my MIP model.

    And when I set PreCrush=1 and add these constraints with cbCut in a MIPNODE callback I can get a feasible solution.

    (2) 

    When you c add these constraints with cbLazy in a MIPSOL callback you get a feasible solution.

    I set certain inequalities (except for the "break symmetry" constraints) as Lazy Constraints, such as:

    c[i][j][k] <= X[i][j][k]

    And when I set LazyConstraints=1 and add these constraints with cbLazy in a MIPSOL callback I can get a feasible solution.

    (3)

    When I set LazyConstraints=1, PreCrush=1 and add the constraints using both cbLazy and cbCut I get an infeasible model.

    This is what I want to express.

    Thanks.

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Ok, so it's not that you're adding all constraints as both lazy constraints and cuts, you split your symmetry breaking constraints into two groups, with one group added as lazy constraints, and one group added as cuts?

    What happens if you just add them all to your model as normal constraints?  Is it infeasible?

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    Hi Riley,

    I am not split the symmetry breaking constraints into lazy constraints and user cut. Certainly, I just add the constraint (34) as user cut. And it is infeasibe when I just add it to my model as normal constraints.

    Secondly, the constraints which added to lazy constraints is hard constraints in my original model, and they are solvable when added as normal constraints.

    Thansk.

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Ok I think I understand.  Your symmetry breaking constraints are user cuts, and there are some other type of constraints (not the symmetry breaking ones) that you are adding as lazy constraints?

    My advice is to add everything as normal constraints, so that you get an infeasible model, then follow our guide How do I determine why my model is infeasible?

    - Riley

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    Hi Riley,

    Yes sir, your understanding is correct.

    My advice is to add everything as normal constraints, so that you get an infeasible model, then follow our guide How do I determine why my model is infeasible?

    So do you mean that If I add the symmetry breaking constraint in the original model, the model is feasible, then adding it as a user cut the model can also be solved?

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    No I think there is a misunderstanding.

    You need to obtain an infeasible model so that you can diagnose infeasibility.

    And it is infeasibe when I just add it to my model as normal constraints.

    You say it is infeasible when you add them as normal constraints, so do this.  Then diagnose infeasibility.

    - Riley

     

    0
  • HAOXING OUYANG
    Conversationalist
    First Question

    But the symmetry breaking constraint is not a necessary constraint in my model, the purpose of adding it is to accelerate the speed of model solving. Do I still need to verify the infeasibility of adding this constraint as normal constraints?

    -Haoxing

    0

Please sign in to leave a comment.