Get branch-and-bound node depth with JuMP

Greetings.

I am facing a problem with JuMP 0.21.4 and Julia 1.6.2. I am working with an Integer Programming model using CPLEX 12.10 and I using callbacks to add some cuts, however, I need these cuts to be added only when the callback is at the root, i.e, I need to add these cuts only when the node depth is 0. Hence, I would like to know if anyone has done something similar. I have used such functionality before when working with another programming language (IBM Docs).

Thank you for your time.

ps: Note that I am not talking about the callback node status (Callbacks · JuMP).

You will need to use a solver-dependent callback: GitHub - jump-dev/CPLEX.jl: Julia interface for the CPLEX optimization software

2 Likes

Very thank you, that is exactly what I was looking for.

Hi, I am sorry for reopening this thread. But I am still struggling to get the branch-and-bound depth through a solver-dependent callback. Follows an MWE based on the example provided at here.

using JuMP, CPLEX, Test

model = direct_model(CPLEX.Optimizer())
set_silent(model)
MOI.set(model, MOI.NumberOfThreads(), 1)

@variable(model, 0 <= x <= 2.5, Int)
@variable(model, 0 <= y <= 2.5, Int)
@objective(model, Max, y)

function my_callback_function(cb_data::CPLEX.CallbackContext, context_id::Clong)
  # preliminary checkings
  context_id != CPX_CALLBACKCONTEXT_CANDIDATE && return
  ispoint_p = Ref{Cint}()
  (CPXcallbackcandidateispoint(cb_data, ispoint_p) != 0 || ispoint_p[]) == 0 && return
  # getting depth
  depth = Ref{Clong}()
#  CPXcallbackgetinfoint(cb_data, CPX_CALLBACK_INFO_NODE_DEPTH, depth)
#  CPXcallbackgetinfolong(cb_data, CPX_CALLBACK_INFO_NODE_DEPTH, depth)
  CPXcallbackgetinfodbl(cb_data, CPX_CALLBACK_INFO_NODE_DEPTH, depth)
  depth > 0 && return
end
MOI.set(model, CPLEX.CallbackFunction(), my_callback_function)
optimize!(model)

When executing the above code, the following output is given:

ERROR: LoadError: MethodError: Cannot `convert` an object of type Int64 to an object of type CPXCALLBACKINFO

As you can see, I already tried replacing the call CPXcallbackgetinfodbl with CPXcallbackgetinfoint and CPXcallbackgetinfolong. But both changes lead to the same error. I suppose that I am using the wrong function or the wrong flag, i.e. I should use something other than CPX_CALLBACK_INFO_NODE_DEPTH. I would like to know if anyone has any documentation about this.

Thank you and best regards.

This is a declared const of type Int64:

CPXcallbackgetinfodbl expects it’s second argument as a CPXCALLBACKINFO CEnum type:

so one of these:

You probably want CPXCALLBACKINFO_NODEDEPTH not CPX_CALLBACK_INFO_NODE_DEPTH. Not a hard mistake to make!

2 Likes

Very thank you for your answer. The code is now executing, however, I think the results are not right. I used the commands as you suggested resulting in the function below:

function my_callback_function(cb_data::CPLEX.CallbackContext, context_id::Clong)  # preliminary checkings
  context_id != CPX_CALLBACKCONTEXT_CANDIDATE && return
  ispoint_p = Ref{Cint}()
  (CPXcallbackcandidateispoint(cb_data, ispoint_p) != 0 || ispoint_p[] == 0) && return  # getting depth
  depth = Ref{Clong}()
  CPXcallbackgetinfolong(cb_data, CPXCALLBACKINFO_NODEDEPTH, depth)
  println(depth[])
end

And I getting really strange results, specifically very big numbers, such as 52465968, 140258102699840,…
I think that the conditions (CPXcallbackcandidateispoint(cb_data, ispoint_p) != 0 || ispoint_p[] == 0) are not enough. Which is strange, since according to the documentation this is a valid condition. If anyone has ever faced such a thing I would like to hear something.

Thank you.

UPDATE 1: Fixing code typing error.

1 Like

Quick thought: are you sure the last bracket is where you want it? If this is a Bool comparison, I would use false over 0, or do you want ispoint_p[] == 0)?

1 Like

Now it is, it was a typing error, even with the bracket at the right place, the error still persists.

On some platforms Clong != CPXLONG. Try:

function my_callback_function(cb_data::CPLEX.CallbackContext, context_id::Clong)
    if context_id != CPX_CALLBACKCONTEXT_CANDIDATE
        return
    end
    ispoint_p = Ref{Cint}()
    ret = CPXcallbackcandidateispoint(cb_data, ispoint_p)
    if ret != 0 || ispoint_p[] == 0
        return
    end
    depth = Ref{CPXLONG}()
    ret = CPXcallbackgetinfolong(cb_data, CPXCALLBACKINFO_NODEDEPTH, depth)
    @assert ret == 0
    println(depth[])
end

Thanks for the answer, but the problem still persists. Follows the complete code so you can check the behavior on your own machine.

using JuMP, CPLEX, Test

model = direct_model(CPLEX.Optimizer())
set_silent(model)
MOI.set(model, MOI.NumberOfThreads(), 1)

@variable(model, 0 <= x <= 2.5, Int)
@variable(model, 0 <= y <= 2.5, Int)
@objective(model, Max, y)

function my_callback_function(cb_data::CPLEX.CallbackContext, context_id::Clong)
#function my_callback_function(cb_data::CPLEX.CallbackContext, context_id::CPXLONG)
  # preliminary checkings
  context_id != CPX_CALLBACKCONTEXT_CANDIDATE && return
#  ispoint_p = Ref{Cint}()
  ispoint_p = Ref{CPXINT}()
  (CPXcallbackcandidateispoint(cb_data, ispoint_p) != 0 || ispoint_p[] == 0) && return
  # getting depth
#  depth = Ref{Clong}()
  depth = Ref{CPXLONG}()
  CPXcallbackgetinfolong(cb_data, CPXCALLBACKINFO_NODEDEPTH, depth)
  println(depth[])
end
MOI.set(model, CPLEX.CallbackFunction(), my_callback_function)
optimize!(model)

Thanks for the consideration.

CPXcallbackgetinfolong(cb_data, CPXCALLBACKINFO_NODEDEPTH, depth)

You need to check the return code to see if the call succeeded. I’m guessing it didn’t. Probably because there was no node and the problem was solved at the root node via heuristics?

More generally, you need to read the CPLEX documentation in more detail: IBM Docs

You say you only want to add cuts at the root, but CPX_CALLBACKCONTEXT_CANDIDATE indicates that you have a valid integer solution. Perhaps you want CPXCALLBACKINFO_NODECOUNT or CPXCALLBACKINFO_NODEUID? instead?

1 Like

Thank you, now it seems to be working.

using JuMP, CPLEX

model = direct_model(CPLEX.Optimizer())
set_silent(model)
MOI.set(model, MOI.NumberOfThreads(), 1)

@variable(model, 0 <= x <= 2.5, Int)
@variable(model, 0 <= y <= 2.5, Int)
@objective(model, Max, y)

function my_callback_function(cb_data::CPLEX.CallbackContext, context_id::Clong)
  ispoint_p = Ref{CPXINT}()
  (CPXcallbackcandidateispoint(cb_data, ispoint_p) != 0 || ispoint_p[] == 0) && return
  n = Ref{CPXLONG}()
  CPXcallbackgetinfolong(cb_data, CPXCALLBACKINFO_NODECOUNT, n)
  println(n[])
end
MOI.set(model, CPLEX.CallbackFunction(), my_callback_function)
optimize!(model)

I just question myself why the above program is printing 0 twice.

Because your callback gets called multiple times at the root node.

Additionally, you are still not checking the return code of CPXcallbackgetinfolong for success.

1 Like