Lazy constraints with callback in gurobi

Hi
I have a model and when of model’s constraints is corresponding to cycles in the graph, so I am trying to add these cycle constraints as lazy constraints within the callback, so in each iteration, I am solving the model search for cycle and adding a lazy constraint as you can see in the code below :

using JuMP
using Gurobi
using Random
#using Test
initial = 0
n = 61
s = 62
E = [(8, 3), (36, 20), (50, 20), (3, 14), (14, 38), (24, 51), (9, 62), (37, 62),
                      (9, 5), (36, 23), (50, 33), (0, 15), (16, 38), (29, 51), (62, 9), (62, 37),
                      (9, 6), (37, 8), (50, 42), (14, 16), (20, 38), (45, 51), (10, 62), (38, 62),
                      (10, 0), (37, 14), (50, 45), (1, 17), (33, 38), (50, 51), (62, 10), (62, 38),
                      (10, 2), (37, 16), (51, 4), (6, 17), (36, 39), (14, 52), (11, 62), (39, 62),
                      (13, 5), (37, 21), (51, 11), (9, 17), (0, 40), (29, 52), (62, 11), (62, 39),
                      (13, 6), (37, 33), (51, 18), (13, 17), (7, 40), (38, 52), (12, 62), (40, 62),
                      (13, 9), (37, 34), (51, 21), (15, 18), (14, 40), (40, 52), (62, 12), (62, 40),
                      (14, 0), (37, 36), (51, 23), (1, 19), (15, 40), (43, 53), (13, 62), (41, 62),
                      (14, 3), (38, 14), (51, 24), (7, 19), (33, 40), (1, 54), (62, 13), (62, 41),
                      (15, 0), (38, 16), (51, 29), (8, 20), (36, 40), (6, 54), (14, 62), (42, 62),
                      (16, 14), (38, 20), (51, 45), (16, 20), (37, 40), (7, 54), (62, 14), (62, 42),
                      (17, 1), (38, 33), (51, 50), (18, 20), (1, 41), (13, 54), (15, 62), (43, 62),
                      (17, 6), (39, 36), (52, 14), (18, 21), (9, 41), (19, 54), (62, 15), (62, 43),
                      (17, 9), (40, 0), (52, 29), (17, 22), (13, 41), (41, 54), (16, 62), (44, 62),
                      (17, 13), (40, 7), (52, 38), (14, 24), (0, 42), (15, 55), (62, 16), (62, 44),
                      (18, 15), (40, 14), (52, 40), (15, 24), (2, 42), (51, 55), (17, 62), (45, 62),
                      (19, 1), (40, 15), (53, 43), (18, 24), (10, 42), (5, 56), (62, 17), (62, 45),
                      (19, 7), (40, 33), (54, 1), (17, 25), (30, 42), (6, 56), (18, 62), (46, 62),
                      (20, 8), (40, 36), (54, 6), (1, 26), (14, 43), (5, 57), (62, 18), (62, 46),
                      (20, 16), (40, 37), (54, 7), (25, 26), (29, 43), (6, 57), (19, 62), (47, 62),
                      (20, 18), (41, 1), (54, 13), (1, 27), (33, 43), (9, 57), (62, 19), (62, 47),
                      (21, 18), (41, 9), (54, 19), (7, 27), (37, 43), (13, 57), (20, 62), (48, 62),
                      (22, 17), (41, 13), (54, 41), (17, 27), (38, 43), (17, 57), (62, 20), (62, 48),
                      (24, 14), (42, 0), (55, 15), (25, 27), (2, 44), (39, 57), (21, 62), (49, 62),
                      (24, 15), (42, 2), (55, 51), (26, 27), (20, 44), (41, 57), (62, 21), (62, 49),
                      (24, 18), (42, 10), (56, 5), (1, 28), (34, 44), (48, 57), (22, 62), (50, 62),
                      (25, 17), (42, 30), (56, 6), (8, 28), (38, 44), (54, 57), (62, 22), (62, 50),
                      (26, 1), (43, 14), (57, 5), (20, 28), (8, 45), (38, 58), (23, 62), (51, 62),
                      (26, 25), (43, 29), (57, 6), (10, 29), (15, 45), (3, 59), (62, 23), (62, 51),
                      (27, 1), (43, 33), (57, 9), (18, 29), (18, 45), (8, 59), (24, 62), (52, 62),
                      (27, 7), (43, 37), (57, 13), (21, 29), (21, 45), (15, 59), (62, 24), (62, 52),
                      (27, 17), (43, 38), (57, 17), (24, 29), (23, 45), (36, 59), (25, 62), (53, 62),
                      (27, 25), (44, 2), (57, 39), (7, 30), (24, 45), (45, 59), (62, 25), (62, 53),
                      (27, 26), (44, 20), (57, 41), (19, 30), (29, 45), (32, 60), (26, 62), (54, 62),
                      (28, 1), (44, 34), (57, 48), (28, 30), (37, 45), (2, 61), (62, 26), (62, 54),
                      (28, 8), (44, 38), (57, 54), (17, 31), (43, 46), (37, 61), (27, 62), (55, 62),
                      (28, 20), (45, 8), (58, 38), (9, 32), (0, 47), (53, 61), (62, 27), (62, 55),
                      (29, 10), (45, 15), (59, 3), (13, 32), (10, 47), (0, 62), (28, 62), (56, 62),
                      (29, 18), (45, 18), (59, 8), (12, 33), (20, 47), (62, 0), (62, 28), (62, 56),
                      (29, 21), (45, 21), (59, 15), (14, 33), (28, 47), (1, 62), (29, 62), (57, 62),
                      (29, 24), (45, 23), (59, 36), (16, 33), (30, 47), (62, 1), (62, 29), (62, 57),
                      (30, 7), (45, 24), (59, 45), (21, 33), (42, 47), (2, 62), (30, 62), (58, 62),
                      (30, 19), (45, 29), (60, 32), (14, 34), (34, 49), (62, 2), (62, 30), (62, 58),
                      (30, 28), (45, 37), (61, 2), (33, 34), (46, 49), (3, 62), (31, 62), (59, 62),
                      (31, 17), (46, 43), (61, 37), (29, 35), (14, 50), (62, 3), (62, 31), (62, 59),
                      (32, 9), (47, 0), (61, 53), (1, 36), (16, 50), (4, 62), (32, 62), (60, 62),
                      (32, 13), (47, 10), (3, 8), (20, 36), (20, 50), (62, 4), (62, 32), (62, 60),
                      (33, 12), (47, 20), (5, 9), (23, 36), (33, 50), (5, 62), (33, 62), (61, 62),
                      (33, 14), (47, 28), (6, 9), (8, 37), (42, 50), (62, 5), (62, 33), (62, 61),
                      (33, 16), (47, 30), (0, 10), (14, 37), (45, 50), (6, 62), (34, 62),
                      (33, 21), (47, 42), (2, 10), (16, 37), (4, 51), (62, 6), (62, 34),
                      (34, 14), (49, 34), (5, 13), (21, 37), (11, 51), (7, 62), (35, 62),
                      (34, 33), (49, 46), (6, 13), (33, 37), (18, 51), (62, 7), (62, 35),
                      (35, 29), (50, 14), (9, 13), (34, 37), (21, 51), (8, 62), (36, 62),
                      (36, 1), (50, 16), (0, 14), (36, 37), (23, 51), (62, 8), (62, 36)]

#####################################################
function example_lazy_constraint()
    cec_model = direct_model(Gurobi.Optimizer())
    set_optimizer_attribute(cec_model, "Threads", 1)
    #set_optimizer_attribute(cec_model, "presolve", 0)
    #################################################
    @variable(cec_model,x[E],binary = true)
    @variable(cec_model, y[v=initial:n],binary = true)  # Bogi: y_s is not needed
    ####################################################
    for i = initial:n
          @constraint(cec_model,sum(x[(i,j)] for j in initial:s if (i,j) in E)
         +sum(x[(j,i)] for j in initial:s if (j,i) in E) == 2*y[i])
    end

    @constraint(cec_model,sum(x[(s,i)] for i=initial:n) == 2)

    for i = initial:n
        for j=initial:s
             if (i,j) in E
                  @constraint(cec_model,x[(i,j)] <= y[i])
              end
             if (j,i) in E
                  @constraint(cec_model,x[(j,i)] <= y[i])
              end
         end
    end

    for (i,j) in E
        if i !=s && j!=s
            @constraint(cec_model,x[(i,j)] >= y[i]+y[j]-1)
        end
    end
    ################################################
    @objective(cec_model, Max, sum(y[i] for i in initial:n))
    cb_calls = Cint[]
    ################################################
    function my_callback_function(cb_data,cb_where::Cint)
              # You can reference variables outside the function as normal
              push!(cb_calls, cb_where)
              # You can select where the callback is run
              if cb_where != GRB_CB_MIPSOL && cb_where != GRB_CB_MIPNODE
                    return # exit
              end
              ##########################################################
              subtours = 0
              current = 0
              nextv = 0
              newsubtours = 0
              T = []
              Next = []
              termination = 0
              prev = 0
              temp = 0
              while termination == 0
                  optimize!(cec_model)
                  #Before querying `callback_value`, you must call:
                  Gurobi.load_callback_variable_primal(cb_data, cb_where)
                  for a in initial:s
                      if callback_value(cb_data, y[a]) > 0
                         append!(T, a)
                      end
                  end
                  T=unique(T)
                  newsubtours = 0
                  while length(T) != 0
                           nextv = rand(T)
                           subtours += 1
                           W = []
                           current = nextv
                           #Before querying `callback_value`, you must call:
                           nodes=findall(b -> ((current,b) in E), T)
                           append!(Next,nodes)
                           Next = unique(Next)
                           prev = first(Next)
                           while current != nextv
                               append!(W,current)
                               posnex = findall(c -> ((current,c) in E) && (c != prev), T)
                               posnex=unique(posnex)
                               temp = rand(posnex)
                               prev = current
                               current = temp
                           end
                           W = unique(W)
                           T = setdiff(T,W)
                           if s in W
                                W= []
                                subtours = subtours-1
                           else
                                newsubtours = newsubtours+1
                           end
                           if length(W) != 0
                                con = @build_constraint(sum(y[node] for node in W) <= length(W)-1)
                                MOI.submit(cec_model, MOI.LazyConstraint(cb_data), con)
                           end
                  end
                  if newsubtours == 0
                     termination=1
                  end
              end
    end
    # You _must_ set this parameter if using lazy constraints.
    MOI.set(cec_model, MOI.RawParameter("LazyConstraints"), 1)
    MOI.set(cec_model, Gurobi.CallbackFunction(), my_callback_function)
    optimize!(cec_model)
end
example_lazy_constraint()

The code is working but going to infinity with really considering the part to add the last constraint !!

What exactly are you trying to do? I see several issues with your code:

  • Typically, in the TSP and related problems, we would like to check for subtours only when we encounter integer-feasible solutions – in order to check if such solutionsare actually valid (i.e., if they have no cycles). Therefore, you should only run your callback when cb_where == GRB_CB_MIPSOL is true.
  • Why do you call optimize! within the callback function (just after while termination == 0)? This causes the model to be optimized again (which will end in the very same callback) and is going to cause a stack overflow error sooner than later.

Today, I made a pull request to include a minimal example on how to solve the Traveling Salesperson Problem (refer to https://github.com/jump-dev/JuMP.jl/pull/2833/files) using JuMP and lazy constraints (also using Gurobi), as this is considered to be a standard technique. I believe that this is exactly what you would like to do.

1 Like