Decision Optimization

Decision Optimization

Delivers prescriptive analytics capabilities and decision intelligence to improve decision-making.

 View Only
Expand all | Collapse all

User cuts slow down root node processing but not other nodes in MIP tree

  • 1.  User cuts slow down root node processing but not other nodes in MIP tree

    Posted Sun February 10, 2019 09:42 PM

    Originally posted by: UserCplex


    Hello,

     

    I have a user defined cut generation that I register with cplex thus:

    int status = CPXsetusercutcallbackfunc(env, mycutcallback, &usercutinfo);

     

    mycutcallback function is like this:

     

    int CPXPUBLIC LP::mycutcallback(CPXCENVptr env, void *cbdata, int wherefrom, void *cbhandle, int *useraction_p)
    {
        int status = 0;

        CUTINFOptr cutinfo = (CUTINFOptr)cbhandle;

        *useraction_p = CPX_CALLBACK_DEFAULT;

        status = CPXgetcallbacknodex(env, cbdata, wherefrom, cutinfo->x, 0, cutinfo->numcols - 1);//Obtain current LP solution at node.
        int depth;
        status = CPXgetcallbacknodeinfo(env, cbdata, wherefrom, 0, CPX_CALLBACK_INFO_NODE_DEPTH, &depth);
        
            int ineqviolated = -1;
            if(depth <= 10) //Identify violations only for nodes that are at depth 10 or lower in the Branch and bound tree.
                ineqviolated = function that identifies if current LP solution violates any of my user cuts and returns 1 if a cut is found, and returns -1 if no cut is violated.
        
        if (ineqviolated != -1) {
            //Add cut to LP using CPXcutcallbackadd(env, cbdata, wherefrom, ctr, rhs[0], sense[0], rmatind, rmatval, CPX_USECUT_PURGE);

            /* Tell CPLEX that cuts have been created */

            *useraction_p = CPX_CALLBACK_SET;
            return 0;
        }
        return (status);

    } /* END mycutcallback */

     

    When the MIP optimization starts, here is what the output looks like (I have snipped the output to be concise and indicated questions as comments in the output itself):

    CPXPARAM_TimeLimit                               7200
    CPXPARAM_Preprocessing_Linear                    0
    CPXPARAM_MIP_Strategy_CallbackReducedLP          0
    Warning: Control callbacks may disable some MIP features.
    MIP emphasis: balance optimality and feasibility.
    MIP search method: traditional branch-and-cut.
    Parallel mode: none, using 1 thread.
    Root relaxation solution time = 0.13 sec. (213.04 ticks)

            Nodes                                         Cuts/
       Node  Left     Objective  IInf  Best Integer    Best Bound    ItCnt     Gap

    *     0+    0                       1.40000e+07        0.0000           100.00%
          0     0        0.0000    77   1.40000e+07        0.0000        0  100.00%
          0     0        0.0000    16   1.40000e+07    MIRcuts: 1        7  100.00%
          0     0        0.0000    33   1.40000e+07    Cuts: 4783       46  100.00%
          0     0        0.0000    71   1.40000e+07      Cuts: 32      186  100.00%
    *     0+    0                           15.0000        0.0000           100.00%
          0     0        0.0000    13       15.0000 UserPurge1: 1      205  100.00%
          0     0        0.0000    16       15.0000 UserPurge1: 1      208  100.00%
          0     0        0.0000    15       15.0000 UserPurge1: 1      208  100.00%
          0     0        0.0000    17       15.0000 UserPurge1: 1      218  100.00%
          0     0        0.0000    10       15.0000 UserPurge1: 1      218  100.00%
          0     0        0.0000    19       15.0000 UserPurge1: 1      257  100.00%

    ////SNIPING 400 ODD SUCH SIMILAR LINES ALL OF WHICH PERTAIN TO THE ROOT NODE ///

    //(q1) What exactly is this CPLEX output telling me?

          0     0        0.0000    47       15.0000 UserPurge1: 1     6806  100.00%
          0     0        0.0000     7       15.0000 UserPurge1: 1     6806  100.00%
    *     0+    0                           11.0000        0.0000           100.00%
    *     0+    0                            9.0000        0.0000           100.00%
          0     2        0.0000     7        9.0000        0.0000     6806  100.00%
    Elapsed time = 191.85 sec. (601574.26 ticks, tree = 0.01 MB, solutions = 4)

    //Once branching starts, the output is lot more concise and processing is also lot quicker than the root node, which takes almost 200 seconds. (see elapsed time above)

    //(Q2) Why is processing at other nodes quicker than root node? What exactly is happening at root node? How is the setting in my user cut separation routine of identifying violated inequalities at nodes at depth of <= 10 influence what is going on?

          3     5        0.0000    20        9.0000        0.0000     6863  100.00%
          4     6        0.0000   129        9.0000        0.0000     7725  100.00%
          6     8        0.0000    63        9.0000        0.0000     7977  100.00%
          8    10        0.0000   101        9.0000        0.0000     8951  100.00%
         10    12        0.0000    78        9.0000        0.0000     8996  100.00%
         11    13        0.0000    73        9.0000        0.0000     9024  100.00%
         12    14        0.0000    87        9.0000        0.0000     9119  100.00%
         14    16        0.0000    51        9.0000        0.0000     9149  100.00%
         16    18        0.0000   125        9.0000        0.0000     9357  100.00%
         29    28        0.0000    62        9.0000        0.0000     9524  100.00%
    Elapsed time = 198.52 sec. (615113.86 ticks, tree = 2.93 MB, solutions = 4)
         46    37        0.0000    32        9.0000        0.0000     9664  100.00%
     

    Repeating my questions here:

    (q1) What exactly is this CPLEX output telling me at the root node? i.e., what is each column telling me. In particular, what is meaning of "UserPurge1: 1"

    (q2) Why is processing at other nodes quicker than root node? What exactly is happening at root node that makes it so much more time consuming than other nodes? How is the setting in my user cut separation routine of identifying violated inequalities at nodes at depth of <= 10 influence what is going on? I would have expected that any invocation of my user cut separation routine (at nodes at depth <= 10) should be as time consuming as the root node itself.

    From the above, I have another question

    (q3) Is there any way to speed up the time spent by CPLEX at the root node?

    Thanks.


    #CPLEXOptimizers
    #DecisionOptimization


  • 2.  Re: User cuts slow down root node processing but not other nodes in MIP tree

    Posted Mon February 11, 2019 01:51 AM

    Do I interpret your output correctly: at the root node you separate exactly one cut for each invocation of the callback?

    Also, can you tell whether your callback ever separates a cut when called from within the tree (with depth >= 1)?


    #CPLEXOptimizers
    #DecisionOptimization


  • 3.  Re: User cuts slow down root node processing but not other nodes in MIP tree

    Posted Mon February 11, 2019 04:13 AM

    Originally posted by: UserCplex


    Daniel,

     

    (1)Yes, each invocation of the user separation routine returns only 1 added inequality via "CPXcutcallbackadd(env, cbdata, wherefrom, ctr, rhs[0], sense[0], rmatind, rmatval, CPX_USECUT_PURGE); "

    To check the other part I changed the code thus:

     

    int cntroot = 0;//global variable keeping track of number of times "CPXcutcallbackadd()" is called from within callback when depth = 0

    int cntnonroot = 0;//global variable keeping track of number of times "CPXcutcallbackadd()" is called from within callback when depth > 0

     

    I modified the callback function thus:

    if (ineqviolated != -1) {//If a violated inequality is found within user callback separation routine.
            if(depth == 0)
                    cntroot++;
            else
                    cntnonroot++;
            if(cntroot + cntnonroot is a multiple of 100)
                    printf("%d \t%d\n", cntroot, cntnonroot);           

             
           //Add cut to LP using CPXcutcallbackadd(env, cbdata, wherefrom, ctr, rhs[0], sense[0], rmatind, rmatval, CPX_USECUT_PURGE);

            /* Tell CPLEX that cuts have been created */

            *useraction_p = CPX_CALLBACK_SET;
            return 0;
     }

    With this, the display (snipped appropriately) of the log is:

     

          0     0        0.0000    12       15.0000 UserPurge1: 1     1520  100.00%
          0     0        0.0000     6       15.0000 UserPurge1: 1     1520  100.00%
    100     0 //Note, this is the display printf() I am doing of cntroot and cntnonroot in code above. I have verified that there are slightly more than 100 lines above this line.
          0     0        0.0000     8       15.0000 UserPurge1: 1     1549  100.00%
    <<snip>> I have verified that between "100     0" and "200    0" below, there are exactly 100 lines of CPLEX display. So, this confirms that for each user cut added at root node, CPLEX log displays one line.
    200     0
          0     0        0.0000    11       15.0000 UserPurge1: 1     2960  100.00%

    <<snip>> I have verified that between "200     0" and "300    0" below, there are exactly 100 lines of CPLEX display. So, this confirms that for each user cut added at root node, CPLEX log displays one line.
    300     0
          0     0        0.0000     7       15.0000 UserPurge1: 1     4499  100.00%

    <<snip>> I have verified that between "300     0" and "400    0" below, there are exactly 100 lines of CPLEX display. So, this confirms that for each user cut added at root node, CPLEX log displays one line.

    400     0
          0     0        0.0000    30       15.0000 UserPurge1: 1     5810  100.00%

    <<snip>> root node processing ends above. Branching begins below.
          0     2        0.0000     7        9.0000        0.0000     6806  100.00%
    Elapsed time = 193.22 sec. (601574.26 ticks, tree = 0.01 MB, solutions = 4)
          3     5        0.0000    20        9.0000        0.0000     6863  100.00%
          4     6        0.0000   129        9.0000        0.0000     7725  100.00%
    456     44 //This tells me that 44 cuts have been separated at nodes (depth <= 10) other than root node
          6     8        0.0000    63        9.0000        0.0000     7977  100.00%

    ...

    No further display of "cntroot      cntnonroot" every appears in the log.

    ----

    So, atleast 44 cuts were separated at non root nodes.

    Do you have any suggestions?

    Would the fact that CPLEX does a printf () of every line on the console for each added inequality at the root node be a possible hotspot?

    Could you let me know what each column in the line of log means in the context of the root node, "UserPurge1: 1"? Is this saying that the inequality I added was not found to be effective by CPLEX and then "purged"?

    Also, even though atleast 44 cuts were separated at nonroot nodes, I did not encounter a line of display in the log at nonroot nodes that included "UserPurge1: 1".

     

    On an unrelated note, I am currently transitioned to linux. In Windows Interactive Optimizer (IO) prompt, the previous series of commands could be circled through by using the arrow keys. In linux, pressing the arrow keys gives rise to stuff like "[[^C" and so on and I am having to retype the entire command at the IO prompt.

     

    Thank you for your time.


    #CPLEXOptimizers
    #DecisionOptimization


  • 4.  Re: User cuts slow down root node processing but not other nodes in MIP tree

    Posted Mon February 11, 2019 06:14 AM

    Quick answer for that off-topic Linux question: Special actions for keys like the arrow-keys are not provided by the interactive itself. They may be provided by the terminal in which you run the program. On Linux you can for example run the interactive optimizer through a program called rlwrap:

    rlwarp cplex

    That should give you scrolling through the history with the arrow keys, for example.

    Before I can answer your CPLEX related questions, we first have to check a few things.


    #CPLEXOptimizers
    #DecisionOptimization


  • 5.  Re: User cuts slow down root node processing but not other nodes in MIP tree

    Posted Tue February 12, 2019 09:44 AM

    Originally posted by: AndreaTramontani


    Hello,


    First, let me try to answer to your specific questions related to the CPLEX log.

    **********************************************************************************************
    1. At the root node, we display information for every iteration of the root node.
    As an example, let me take a snapshot of your log:

        Nodes                                         Cuts/
       Node  Left     Objective  IInf  Best Integer    Best Bound    ItCnt     Gap
          0     0        0.0000    33   1.40000e+07    Cuts: 4783       46  100.00%
          0     0        0.0000    71   1.40000e+07      Cuts: 32      186  100.00%
    *     0+    0                           15.0000        0.0000           100.00%
          0     0        0.0000    13       15.0000 UserPurge1: 1      205  100.00%

    First line indicates that:
    .. the current Objective of the LP relaxation (a lower bound for the whole problem, given that you are at the root node) has value 0
    .. the fractional solution has 33 integer infeasibilities (33 integer or binary variables with fractional value in the solution)
    .. the incumbent solution has value 1.4e+7 (this is an upper bound)
    .. 4783 cuts (a mix of CPLEX cuts and user cuts) have been added to the problem in this round
    .. 46 simplex iteration have been spent so far to solve the problem, from the beginning of optimization
    .. the gap between upper bound UB and lower bound LB, calculated as (UB-LB)/(eps+|UB|), is 100% (obvious, since LB = 0).

    Third line indicates that a heuristic has found an incumbent of value 15.0, still the best lower bound so far is 0, so the gap is still 100%.

    Fourth line is the same as first line, but it reports "UserPurge1: 1" for the cuts.
    This means that no CPLEX cut has been added to the problem, so CPLEX by itself would have aborted the cut loop, but one user cut with key "UserPurge1" has been added, so CPLEX will continue the loop.
    The key "UserPurge1" means that the cut is a user cut that CPLEX is allowed to purge. It does not mean that the cut has been purged, but that it was added to the problem and it could be purged later on. Unfortunately, CPLEX does not display information on the number of purged cuts.

     

    2. In the tree, we display at most one line per node processed, and by default:
    .. we do not display information at every node
    .. we do not display information on number of applied cuts.
    The verbosity of the log can be controlled by those two parameters: CPXPARAM_MIP_Display and CPXPARAM_MIP_Interval.
    In particular, to see the number of applied cuts in the tree, you need to set CPXPARAM_MIP_Display to 4.
    Please note that there is currently a small bug in the display of number of cuts applied in the tree, visible only if you set CPXPARAM_MIP_Display to 4.
    By mistake, in the tree we display twice the cuts that are added. This issue has been now fixed and the fix will be available in the next release, CPLEX 12.9.0.
    **********************************************************************************************


    Then, let me try to give you some performance hints related to your case.

    **********************************************************************************************
    1. In general, CPLEX applies quite heavy machinary at the root node.
    At every pass of the root cut loop, it applies heuristics, probing, aggressive separation of cuts, etc.
    The same machinery is applied also at the nodes in the tree, but much less aggressively (probing, heuristics, cuts are applied only at some nodes, and in a much more conservative way).
    So it is somehow expected that the root node can take much more than a single node.

     

    2. Nevertheless, it seems that in your specific case the root node could be shortened and you need to find a way to reduce the number of iterations.
    Looking at the evolution of the lower bound (always stuck at 0), I'm wondering if your usercuts are really helpful.
    In any case, here's few suggestions to try to make the root node shorter.

    .. If possible, add more user cuts per iteration. Adding only one cut per iteration and then having to resolve the problem at every iteration is in general not a good choice.

    .. Since the lower bound never moves (it is stuck at 0), maybe you are repeatedly adding the same user cuts that then gets purged, and separated again, and so on.
    If possible, you can implement, in your callback, a check to avoid separating/adding twice the same cut.
    Or you can simply try to add the cuts with CPX_USECUT_FORCE. In this way every (violated) cut is added exactly once, and hopefully the root cut loop will stop much earlier, when you do not find violated cuts anymore. But note that you have to check for violation yourself, because with CPX_USECUT_FORCE and CPX_USECUT_PURGE CPLEX adds usercuts even if not violated.

    .. By default, CPLEX adopts various criteria to decide when to stop separating cuts at the root node.
    However, with a usercut callback, CPLEX will never stop the root cut loop as long as the user adds cuts. It is up to the user to decide when to break the cut loop.
    A simple way is to limit the number of cut passes with the parameter CPXPARAM_MIP_Limits_CutPasses.
    Otherwise, you can implement more elaborated criteria. For instance, you can use CPXgetcallbacknodeinfo() to query various information, like the value of the lower bound, of the upper bound, etc, and break the separation if you don't observe enough progress for some iterations.

     

    3. Even if it seems that in the tree you are not seeing performance issues, please pay attention at not forcing too many cut loop iterations for the same node in the tree.
    .. I see that you separate cuts only if depth <= 10, which of course limits the number of nodes for which you apply cuts.
    .. But there is no limit on the cut loop iterations for those nodes. An easy thing you can try is the following.
    When your callback is invoked in the tree, you can query the sequential nodeid of the node, again with CPXgetcallbacknodeinfo(). Then, for every nodeid, you can limit the number of times you separate cuts, to make sure that, at every node in which you decide to separate cuts, the number of iterations of the node cut loop is under control.
    **********************************************************************************************


    Hope this can help you.
    Andrea


    #CPLEXOptimizers
    #DecisionOptimization


  • 6.  Re: User cuts slow down root node processing but not other nodes in MIP tree

    Posted Wed February 13, 2019 12:39 AM

    Originally posted by: UserCplex


    Andrea,

     

    Thanks for your input. They are detailed and helpful.

    In my problem, the optimal solution is infact 0. So, a large amount of branch and bound tree nodes or cuts are needed before the lower and upper bound match.

    Regarding your suggestion of usage of CPXPARAM_MIP_Limits_CutPasses, I have a few queries:

    (Q1)

    From https://www.ibm.com/support/knowledgecenter/en/SS9UKU_12.5.0/com.ibm.cplex.zos.help/Parameters/topics/CutPass.html this applies only at the root node and this limits the number of times 

    int CPXPUBLIC LP::mycutcallback(CPXCENVptr env, void *cbdata, int wherefrom, void *cbhandle, int *useraction_p) 

    is called. Within each call, I may very well choose to add 1 cut or 10 cuts.

    That is, if CPXPARAM_MIP_Limits_CutPasses is set to 10. Then, the user callback function will be called at the root node at most 10 times regardless of whether each call to that function adds 1 cut or 20 cuts.  Is my understanding correct?

    (Q2) Is there any parameter that is the equivalent of this for non-root nodes? If there was then setting this also to a nonzero positive limit should help control the number of iterations of the node cut loop.

    (Q3) Is there any parameter that limits the number of actual cuts added by the user in the user cut callback for a node? I understand that this can only be approximate (more specifically a lower bound) since within the callback function, I may add 1 cut or 20 cuts during one pass and this number if not known a priori leading to shooting above the parameter limit.

    Thank you.


    #CPLEXOptimizers
    #DecisionOptimization


  • 7.  Re: User cuts slow down root node processing but not other nodes in MIP tree

    Posted Wed April 17, 2019 10:16 AM

    Originally posted by: AndreaTramontani


    Hello,

    here's some replies to your last questions.

    Andrea.
     

    > (Q1)
    > From https://www.ibm.com/support/knowledgecenter/en/SS9UKU_12.5.0/com.ibm.cplex.zos.help/Parameters/topics/CutPass.html
    > this applies only at the root node and this limits the number of times
    > int CPXPUBLIC LP::mycutcallback(CPXCENVptr env, void *cbdata, int wherefrom, void *cbhandle, int *useraction_p)
    > is called. Within each call, I may very well choose to add 1 cut or 10 cuts.
    > That is, if CPXPARAM_MIP_Limits_CutPasses is set to 10. Then, the user callback function will be called at the root node at most 10 times
    > regardless of whether each call to that function adds 1 cut or 20 cuts.  Is my understanding correct?

    Yes, your understanding is correct. If CPXPARAM_MIP_Limits_CutPasses is set to 10, CPLEX will apply at most 10 cut passes at the root, regardless the number of cuts added at each pass.
    Of course, if at given pass no cut is added, CPLEX will abort the cut loop earlier, so CPXPARAM_MIP_Limits_CutPasses is an upper bound on the number of passes.

    Please let me add two (maybe relevant) details, even if they are not directly related to CPXPARAM_MIP_Limits_CutPasses.

    The usercut callback has two arguments:

    1. wherefrom (input parameter).
    This can be either CPX_CALLBACK_MIP_CUT_LOOP or CPX_CALLBACK_MIP_CUT_LAST.
    At every cut pass, CPLEX first invokes the callback with wherefrom = CPX_CALLBACK_MIP_CUT_LOOP.
    If and only if no cut is added (neither CPLEX cuts nor usercuts) then at the same cut pass CPLEX also invokes the callback with wherefrom = CPX_CALLBACK_MIP_CUT_LAST.
    This is done do leave the possibility to the user to decide if he wants to separate his own cuts at every pass (i.e., when wherefrom = CPX_CALLBACK_MIP_CUT_LOOP)
    or only when CPLEX is not able to add (or decides to not add) any cut (i.e., when wherefrom = CPX_CALLBACK_MIP_CUT_LAST).
    So, at a given pass,
    .. if you do not add any cut when the usercut callback is invoked with wherefrom = CPX_CALLBACK_MIP_CUT_LOOP, and
    .. if CPLEX as well does not add any cut
    then the usercut callback will be also invoked with wherefrom = CPX_CALLBACK_MIP_CUT_LAST.

    2. useraction_p (output parameter).
    This can be used to force CPLEX to abort the cut loop.

    For both arguments, you can find more details in the docs:
    https://www.ibm.com/support/knowledgecenter/SSSA5P_12.9.0/ilog.odms.cplex.help/refcallablelibrary/mipapi/setusercutcallbackfunc.html


    > (Q2) Is there any parameter that is the equivalent of this for non-root nodes?
    > If there was then setting this also to a nonzero positive limit should help control the number of iterations of the node cut loop.

    No, the parameter has no effect on the number of cut passes applied in the cut loop of a given node in the tree.
    If you set it to -1, then CPLEX will not apply any of its cuts, but the parameter does not affect the number of times the callback is called.

    As I wrote above, you can control the number of cut passes using the "useraction_p" parameter.
    For instance, at any node, you can query the sequential number of the node (which is a unique id of the node), using CPXXgetcallbacknodeinfo().
    Then, knowing at which node your callback is called from, you can decide to apply as many cut passes as you want and, when you instead want
    to abort the cut loop, you can simply force it using "useraction_p" parameter.

    See https://www.ibm.com/support/knowledgecenter/SSSA5P_12.9.0/ilog.odms.cplex.help/refcallablelibrary/mipapi/getcallbacknodeinfo.html
    for more details on the nodeinfo you can query from a usercut callback.
     

    > (Q3) Is there any parameter that limits the number of actual cuts added by the user in the user cut callback for a node?
    > I understand that this can only be approximate (more specifically a lower bound) since within the callback function,
    > I may add 1 cut or 20 cuts during one pass and this number if not known a priori leading to shooting above the parameter limit.

    No, there is no limit on the number of cuts you can add from a single call of the usercut callback. It is up to the user to decide how many cuts to add.

     


    #CPLEXOptimizers
    #DecisionOptimization


  • 8.  Re: User cuts slow down root node processing but not other nodes in MIP tree

    Posted Sat April 27, 2019 11:52 AM

    Originally posted by: UserCplex


    Andrea,

     

    I am trying to implement a finite number of cutpasses within non-root nodes. To keep things simple, let us work with a single thread.

    From your response,

     

    For instance, at any node, you can query the sequential number of the node (which is a unique id of the node), using CPXXgetcallbacknodeinfo().

     

    it is not fully clear how I should be using this information.

    I was thinking of structuring my usercut callback function thus if I want to run my separation routines, say at most 5 times within any branch and bound node:

     

    int CPXPUBLIC LP::mycutcallback(CPXCENVptr env, void *cbdata, int wherefrom, void *cbhandle, int *useraction_p){

       *useraction_p = CPX_CALLBACK_DEFAULT;

        {

               if this function is being called for the first time within a B&B Tree Node,  set counter = 0; // is there a way to figure out this condition using wherefrom?

               if (counter < 5){

                      run my separation routine to identify violated cuts.

                      if(violated cuts at current LP solution are found) {

                                     add cuts to LP subproblem

                                     counter++;

                                    *useraction_p = CPX_CALLBACK_SET;

                     }

              }

        }

          return 0;

    }

     

    The question I have (indicated in comments above) is whether I can use wherefrom to figure out whether this callback is being called for the first time from a node. If yes, I had planned to set a global counter variable to 0. Then, when a cut pass happens, this counter is incremented by 1.

    However, the only values of wherefrom seem to be CPX_CALLBACK_MIP_CUT_LOOP and CPX_CALLBACK_MIP_CUT_LAST, neither of which help establish whether it is being called for the first time or not.

     

    Could you please provide some pseudocode of how using sequential number will help here? In a single threaded application can I just store the previous encountered sequential number of the node and the very first time that I encounter a new sequential number, is it guaranteed that that user cut callback is being called for the very first time from a new node? That way, I can have one integer variable store the most recently encountered sequential number. Also, a possibly equivalent question is, when a new sequential number is encountered, (in a single threaded application), is it guaranteed that work on the previous (now going to be overwritten) sequential number node is fully complete and never will that node be entered to again?

    Thank you.


    #CPLEXOptimizers
    #DecisionOptimization


  • 9.  Re: User cuts slow down root node processing but not other nodes in MIP tree

    Posted Tue April 30, 2019 05:16 PM

    Originally posted by: AndreaTramontani


    Hello,

    Here's an answer to your last question.

    Andrea


    > In a single threaded application can I just store the previous encountered sequential number
    > of the node and the very first time that I encounter a new sequential number,
    > is it guaranteed that that user cut callback is being called for the very first time from a new node?

    Yes, exactly.

     

    > Could you please provide some pseudocode of how using sequential number will help here?

    The pseudocode below should work in a single threaded application.

     

    /* Declaration of the data structure for the user callback */
    typedef struct {
       int counter;
       CPXLONG seqnum;
    } MYCBHANDLE;
    
    
    /* Entry point */
    int
    main (int  argc, char *argv[])
    {
       int status = 0;
    
       /* Init the callback handle */
       MYCBHANDLE mycbhandle;
       mycbhandle.counter = 0;
       mycbhandle.seqnum = -1;
    
       ...
       ...
       
       /* Set up the function mycutcallback to generate user cuts */
       status = CPXXsetusercutcallbackfunc (env, mycutcallback,
                                            &mycbhandle);
       if ( status ) goto TERMINATE;
    
       /* Solve problem */
       status = CPXXmipopt (env, lp);
       if ( status ) goto TERMINATE;
    
       ...
       ...
    
    }
    
    /* This function implements the usercut callback. */
    static int CPXPUBLIC
    cutcallback (CPXCENVptr env, void *cbdata, int wherefrom,
                 void *cbhandle, int *useraction_p)
    {
       int status = 0;
    
       /* Get cbhandle */
       MYCBHANDLE *mycbhandle = (MYCBHANDLE *) cbhandle;
       
       /* Sequential number of the node */
       CPXLONG seqnum;
    
       /* Init useraction */
       *useraction_p = CPX_CALLBACK_DEFAULT;
    
       /* Query sequential number of node. */
       status = CPXgetcallbacknodeinfo (env, cbdata, wherefrom, 0,
                                        CPX_CALLBACK_INFO_NODE_SEQNUM_LONG,
                                        &seqnum);
       if ( status ) goto TERMINATE;
    
       if ( seqnum == mycbhandle->seqnum ) {
          /* Node not changed, increase number of calls */
          ++(mycbhandle->counter);
       }
       else {
          /* New node, update node number and reset number of calls */
          mycbhandle->seqnum = seqnum;
          mycbhandle->counter = 0;
       }
    
       if ( mycbhandle->counter < 5 ) {
          /* Separate cuts here */
          ...
          ...
          *useraction_p = CPX_CALLBACK_SET;
       }
       else {
          /* Abort the loop */
          *useraction_p = CPX_CALLBACK_ABORT_CUT_LOOP;
       }
       
       ...
       ...
    
    } 
    

     

    Note that it is not hard to extend the code above to a multi-threaded application, because the sequential number of a node is still a unique identifier of a node.
    What you need to do in that case is to extend the data structure MYCBHANDLE.
    Namely, "counter" and "seqnum" must be arrays of size "# of threads", storing one value for each thread used.

    When the usercut callback is called, you can query from which thread it is called from, with
       CPXXgetcallbackinfo (env, cbdata, wherefrom, CPX_CALLBACK_INFO_MY_THREAD_NUM, &mythread);
    and then you can operate on mycbhandle->counter[mythread] and mycbhandle->seqnum[mythread].

     


    #CPLEXOptimizers
    #DecisionOptimization