0

So I'm curious how to get the number of branch and bounds nodes explored. I was interested in this to gage how hard solving my IP is - if there is a better metric for this please feel free to share.

I tried to use

my_prob.solution.MIP.get_incumbent_node()

which from the CPLEX documentation gives the node number which the solution was found - which I would think would be a good gage of number of nodes explored. But it always returns 0, I attached an example where the input (guess) solution and the output are different. I would expect it to be 0 only if the input solution was the global optimimal solution (the root node found strong duality).

Here is a small example:

Thank you

import cplex
from cplex.exceptions import CplexError

# my_prob = cplex.Cplex()

# print(dir(my_prob))
# print("-----------")
# print(dir(my_prob.solution))
# print(my_prob.solution.get_status_string())
# print(my_prob.solution.get_status())
# print(my_prob.solution.get_method())



class knapsack:
    def __init__(self,N,g,square_list,density_list,cs,dx,number_squares):
        self.N = N
        self.square_list= square_list
        self.g = g
        self.nummats= len(density_list)
        self.numlist=[]
        self.cs=cs
        self.number_squares=number_squares
        self.dx=dx
        self.density_list=density_list
    def solve_problem(self):
        number_squares=self.number_squares
        square_list=self.square_list
        nummats=self.nummats
        density_list=self.density_list
        dx=self.dx
        #lol_print([number_squares,square_list,nummats,density_list,dx])

        try:
            my_prob = cplex.Cplex()
            prob =my_prob
            prob.set_log_stream(None)
            prob.set_error_stream(None)
            prob.set_warning_stream(None)
            prob.set_results_stream(None)
            my_obj = self.g
            my_ctype = "B"
            number_of_one = self.square_list.count(1.0)
            my_ctype = my_ctype*len(self.square_list)
            val = self.N  -number_of_one

            val2=self.cs

            #print(val)
            #print(len(self.g))
            #print(len(self.square_list))
            rhs=[val]
            my_sense="L"
            my_rownames = ["r1"]

            counter =0
            variable_list=[]
            coiff_list=[]
            coiff_den =[]
            for k in range(0,number_squares):
                sub_slice = square_list[k*nummats:k*nummats+nummats]
                #print(sub_slice)
                #print(len(sub_slice))
                temp1=[0]*nummats
                temp2 = sub_slice

                for j in range(0,len(sub_slice)):
                    temp1[j] = density_list[j]*dx**2

                    if(temp2[j]==0):
                        temp2[j] = 1.0
                    else:
                        temp2[j]=-1.0
                    variable_list.append(str(counter))
                    self.numlist.append(counter)
                    counter+=1

                coiff_den = coiff_den + temp1
                coiff_list  = coiff_list + temp2 
            #print(square_list)
            #print(coiff_den)
            #print(coiff_list)

            rhs2 =[val2]
            #print("got here")
            rows = [[variable_list, coiff_list]]
            rows2  =[[variable_list,coiff_den]]
            prob.objective.set_sense(prob.objective.sense.minimize)

            prob.variables.add(obj=my_obj, types=my_ctype,
                        names=variable_list)
            prob.linear_constraints.add(lin_expr=rows, senses=my_sense,
                                    rhs=rhs)
            prob.linear_constraints.add(lin_expr=rows2, senses=my_sense,rhs=rhs2)

            for i in range(0,number_squares):
                sos_var = variable_list[i*nummats:i*nummats+nummats]
                num_var = self.numlist[i*nummats:i*nummats+nummats]
                #print((sos_var,num_var))
                prob.SOS.add(type="1", SOS=[sos_var, num_var])



            my_prob.solve()
            x = my_prob.solution.get_values()
            my_prob.solution.write("myanswer")
            #print(x)
            #print(my_prob.solution.get_status_string())
            #print(my_prob.solution.get_quality_metrics())
            #print(dir(my_prob.solution.MIP))
            #print(my_prob.solution.MIP.get_incumbent_node())
            #print(my_prob.get_stats())
            print("starting" + str(square_list))
            print("solution :" + str(x))
            print("what I think returns number of nodes (wrong): " + str(my_prob.solution.MIP.get_incumbent_node()))
            #print(my_prob.solution.get_integer_quality())
            #print(self.cs)
            mysum=0.0
            for k in range(0,number_squares):
                sub_slice = x[k*nummats:k*nummats+nummats]

                for j in range(0,len(sub_slice)):
                    mysum = mysum+sub_slice[j]*density_list[j]*dx**2
            #print(mysum)
            #print(self.cs)
            #print(coiff_den)


            return (x,my_prob.solution.get_status_string())
        except CplexError as exc:
            #print(exc)
            mysum=0.0
            for k in range(0,number_squares):
                sub_slice = square_list[k*nummats:k*nummats+nummats]

                for j in range(0,len(sub_slice)):
                    mysum = mysum+sub_slice[j]*density_list[j]*dx**2
            #print(mysum)
            #print(self.cs)
            return None

def lol_print(arr):
    for k in arr:
        print(k)   


lol = knapsack(2,[1,-1,-1,-1,],[0,0,0,0],[1,2],10.0,.1,2)
lol.solve_problem() 
Eigenvalue
  • 1,093
  • 1
  • 14
  • 35

2 Answers2

1

If you comment out the following code, the engine log will be shown on stdout:

    prob.set_log_stream(None)
    prob.set_error_stream(None)
    prob.set_warning_stream(None)
    prob.set_results_stream(None)

And, the engine log, looks like this:

Version identifier: 12.10.0.0 | 2019-11-26 | 843d4de
CPXPARAM_Read_DataCheck                          1
Found incumbent of value 0.000000 after 0.00 sec. (0.00 ticks)
MIP Presolve eliminated 1 redundant SOS constraints.
Tried aggregator 1 time.
MIP Presolve eliminated 2 rows and 2 columns.
Reduced MIP has 0 rows, 2 columns, and 0 nonzeros.
Reduced MIP has 2 binaries, 0 generals, 1 SOSs, and 0 indicators.
Presolve time = 0.00 sec. (0.00 ticks)
Probing time = 0.00 sec. (0.00 ticks)
Tried aggregator 1 time.
MIP Presolve eliminated 1 rows and 2 columns.
All rows and columns eliminated.
Presolve time = 0.00 sec. (0.00 ticks)

Root node processing (before b&c):
  Real time             =    0.00 sec. (0.01 ticks)
Parallel b&c, 8 threads:
  Real time             =    0.00 sec. (0.00 ticks)
  Sync time (average)   =    0.00 sec.
  Wait time (average)   =    0.00 sec.
                          ------------
Total (root+branch&cut) =    0.00 sec. (0.01 ticks)

From that we can see that the solution was found in presolve (branch and bound was not needed), so my_prob.solution.MIP.get_incumbent_node() is returning the correct result.

rkersh
  • 4,447
  • 2
  • 22
  • 31
  • Thanks a lot for the reply rkersh - what is happening/being used in this 'presolve' – Eigenvalue Mar 10 '20 at 19:57
  • A short introduction to presolve can be found in the documentation [here](https://www.ibm.com/support/knowledgecenter/SSSA5P_12.10.0/ilog.odms.cplex.help/CPLEX/UsrMan/topics/progr_adv/presolve_adv/02_presolve_intro.html). – rkersh Mar 10 '20 at 20:04
  • Got it- thanks a lot for the quick help, I really appreciate it! – Eigenvalue Mar 10 '20 at 21:52
  • Any documentation on obtaining the number of cuts and the type of cuts used in a problem - I found the my_prob.solution.MIP.cut_type my_prob.solution.MIP.get_num_cuts but they are notbehaving how I would expect. – Eigenvalue Mar 15 '20 at 07:26
  • [Cplex.solution.MIP.get_num_cuts](https://www.ibm.com/support/knowledgecenter/SSSA5P_12.10.0/ilog.odms.cplex.help/refpythoncplex/html/cplex._internal._subinterfaces.MIPSolutionInterface-class.html#get_num_cuts) is indeed the correct method to query the number of cuts used in a problem. Similarly, [Cplex.solution.MIP.cut_type](https://www.ibm.com/support/knowledgecenter/SSSA5P_12.10.0/ilog.odms.cplex.help/refpythoncplex/html/cplex._internal._subinterfaces.CutType-class.html) contains the cut type values that can be queried. Sounds like you should ask a new question. – rkersh Mar 16 '20 at 14:04
  • @rkersh This just shows that in the question asked the number of nodes expanded is zero and hence get_incumbent_node() is incidentally returning the correct number of nodes expanded, right? How do you actually access the number of nodes expanded in CPLEX? – tc1729 Jul 10 '21 at 17:50
0

In the Python API of CPLEX 22.1.0.0, there seem to be two node counts that could be useful:

my_prob.solution.progress.get_num_nodes_processed() and my_prob.solution.progress.get_num_nodes_remaining()

sbay
  • 3
  • 1