0

I've a C++ app which can use indistinctively CPLEX or COIN, and now I want to add SCIP as a choice. As a part of the functionality, it's required to run the LP relaxation of a MIP problem. In CPLEX or COIN that's simple, I just create the problem and then solve the LP using:

  • CPLEX: int const rval = CPXXlpopt(m_env, m_lp);
  • COIN: m_modelIP.solver()->getModelPtr().initialSolve(m_lpOptions);

And the solution to that LP is in the same place the solution to the regular MIP would be, there's no "special" nor "separate" solution object I need to look at, just the regular one.

However, I haven't been able to figure out the equivalent using SCIP. So far, I've seen these two possible solutions to achieve that:

    int nVars = SCIPgetNVars(m_scip);
    std::vector<SCIP_VARTYPE> varTypes(nVars, SCIP_VARTYPE_CONTINUOUS);
    SCIP_VAR **pVars = SCIPgetVars(m_scip);
    
    /* Move all binary and integral variables to continuous */
    for (size_t i = 0; i < static_cast<size_t>(nVars); i++)
    {
        SCIP_VARTYPE const varType = SCIPvarGetType(pVars[i]);
        if (varType == SCIP_VARTYPE_CONTINUOUS)
            continue;

        varTypes[i] = varType;
    
        SCIP_Bool infeasible;
        if (SCIPchgVarType(m_scip, pVars[i], SCIP_VARTYPE_CONTINUOUS, &infeasible) != SCIP_OKAY)
            throw std::runtime_error("Error changing integrality type to SCIP variable " + std::to_string(i) + " to continuos to solve LP relaxation");
    }

    /* Store parameters current value */
    SCIP_Longint oldNodes;
    if (SCIPgetLongintParam(m_scip, "limits/nodes", &oldNodes) != SCIP_OKAY)
        throw std::runtime_error("Couldn't get limits/nodes parameter in SCIP");

    int oldMaxRounds;
    if (SCIPgetIntParam(m_scip, "presolving/maxrounds", &oldMaxRounds) != SCIP_OKAY)
        throw std::runtime_error("Couldn't get presolving/maxrounds parameter in SCIP");
        
    /* Change parameters to create the "LP-like" scenario */
    if (SCIPsetLongintParam(m_scip, "limits/nodes", 1) != SCIP_OKAY)
        throw std::runtime_error("Couldn't set limits/nodes parameter in SCIP");

    if (SCIPsetIntParam(m_scip, "presolving/maxrounds", 0) != SCIP_OKAY)
        throw std::runtime_error("Couldn't set presolving/maxrounds parameter in SCIP");

    /* Solve the supposedly now LP problem */
    if (SCIPsolve(m_scip) != SCIP_OKAY)
        throw std::runtime_error("Couldn't solve LP problem in SCIP");

    /* Restore the original state of the parameters */
    if (SCIPsetLongintParam(m_scip, "limits/nodes", oldNodes) != SCIP_OKAY)
        throw std::runtime_error("Couldn't restore limits/nodes parameter in SCIP");

    if (SCIPsetIntParam(m_scip, "presolving/maxrounds", oldMaxRounds) != SCIP_OKAY)
        throw std::runtime_error("Couldn't restore presolving/maxrounds parameter in SCIP");

    /* Restore the original state of the variables */
    for (size_t i = 0; i < static_cast<size_t>(nVars); i++)
    {
        SCIP_VARTYPE const varType = varTypes[i];
        if (varType == SCIP_VARTYPE_CONTINUOUS)
            continue;

        SCIP_Bool infeasible;
        if (SCIPchgVarType(m_scip, pVars[i], varType, &infeasible) != SCIP_OKAY)
            throw std::runtime_error("Error restoring integrality type to SCIP variable " + std::to_string(i) + " from continuos after solving LP relaxation");
    }
  • Method 2: Use the LP interface of SCIP.
    /* Presolve the original MIP problem. Without this step, the following SCIPgetLPI() call throws a SIGSEGV */
    if (SCIPpresolve(m_scip) != SCIP_OKAY)
        throw std::runtime_error("Couldn't presolve SCIP");

    /* Access the LP interface of SCIP */
    SCIP_LPI *pLpi = nullptr;
    if (SCIPgetLPI(m_scip, &pLpi) != SCIP_OKAY)
        throw std::runtime_error("Couldn't obtain access to LP interface of SCIP");

    /* And solve it */
    if (SCIPlpiSolvePrimal(pLpi) != SCIP_OKAY)
        throw std::runtime_error("Couldn't solve LP interface of SCIP");

Although method 1 seems to work, I have the (uncorroborated) feeling that method 2 is more correct. However, I'm not sure that the method used to solve the problem (SCIPlpiSolvePrimal()) is indeed the correct one, or if the solution provided by that method is located in such a place that I can simply retrieve it using SCIPgetBestSol() (My library doesn't have separated methods to obtain the IP or the LP solution, since neither CPLEX nor COIN really need it).

SO my main question is: Is my approach #1 correct? Or is it too slow/not the most appropiate one, and instead #2 is the one that should be used instead? If so, is the solution provided by SCIPlpiSolvePrimal() accessible from the regular MIP interface? Or if not, is there a way to move it into that?

Or is there even a third way to tackle this case? I've read here that maybe using SCIP*Dive() is a better approach, but I haven't found any samples regarding that.

Thanks a lot for your help.

  • I dont know SCIP, but doing the relaxation manually as in #1 must be the fasted way as well. relaxed MIP is nothing but this. you might be intrested in https://github.com/google/or-tools which provides a wrapper for all familiar solvers, which probably also includes SCIP. So you can let google handle your relaxation, if you want – draz Jan 04 '22 at 19:18
  • Hi @draz. Indeed, I based most of my development in comparing the interface for CPLEX and COIN to the one for SCIP on ORTools (and also comparing the one for CPLEX and SCIP in COIN-CBC). But using directly ORTools in my code is a major change I can't afford right now. – Alvaro Palma Aste Jan 04 '22 at 19:26
  • its almost impossible that there is no simple call in SCIP for the basic relaxation. on the other hand, you already have the code to do it manually - unless there is a bug, why not use it... – draz Jan 04 '22 at 19:39
  • My biggest concern in using method #1 is that it works in a per-variable basis, and, from my experience in COIN and CPLEX, doing that (working each variable separately) can be painfully slow when the number of variables is really big (as in my case, I can have in the order of 10e6 vars). – Alvaro Palma Aste Jan 04 '22 at 19:49
  • Note that MIP presolve ≠ LP presolve. – Erwin Kalvelagen Jan 05 '22 at 12:42
  • Thanks @ErwinKalvelagen , I wasn't aware of that. Since that's the case, what would be a good alternative to access the LP interface using `SCIPgetLPI()` instead? Because that LP interface can only be accessed if the problem is in the **solving** state (trying to access it as soon as the problem has been defined leads to a `SIGSEGV`) – Alvaro Palma Aste Jan 05 '22 at 13:34

1 Answers1

2

If it is possible for you to execute your code inside a SCIP plugin, then that would be the most efficient/elegant way to do it. (You could, e.g. add an event handler that is executed after the first LP solve and just gets the LP solution). This is also where you could use the diving mode to change some aspects of the problem, just solve the (changed) LP and then resume solving the original problem. You cannot do without using plugins, because you need to be in the solving stage to execute diving mdoe. Method 2 that you mentioned has the same issue, the LPI will only have the correct information if you are already in solving mode. Of course you could create a new LPI struct and basically copy the whole problem, but that is probably too inefficient if you are solving large problems.

If you need your code structure to be the same as it is (call LP solve, do something with the information, continue solving) you are stuck with option 1 for now (the changing of variable types will not be the performance problem, but you will essentially restart the solve after changing the variable types).

Unfortunately, SCIP currently does not have the functionality to just solve the relaxation and then stop after, with the option of continuing the whole MIP solve.

I hope this helps, let me know if you something with my answer is not clear.

Leon
  • 1,588
  • 1
  • 7
  • 16
  • Actually, my intention is simply be able to configure a MIP problem, while being able to solve it as if it were an LP. It's NOT my goal to continue solving the MIP once the LP has been solved. I guess alternative #1 should be OK, but I wonder if that's the most efficient way to do it, considering I'm using A LOT of variables. That's why I wonder if alternative #2 would be OK, although, even if that works, I've no idea how to make the resulting solution available via the regular `SCIPgetBestSol()` (because another goal is to use the same code to solve the LP and the MIP). – Alvaro Palma Aste Jan 11 '22 at 14:01
  • So why do you need it to be a MIP at all? Could you not simply write an application that reads in a MIP-file, saves the integer-status of the variables in some internal data structure, then changes the problem to a pure LP and solves it? – Leon Jan 12 '22 at 15:21
  • The idea is to receive a generic problem (most likely a MIP) and, depending on runtime settings, be able to solve the LP relaxation to obtain an upper boundary, or just go ahead and solve the MIP itself. Alternative #1 works OK, but I wonder if there would be a way to do it in a more "cleaner" way using the LP interface of SCIP. The main issue is that I don't know upfront if I'm going to have to run the LP or the MIP, that's up to the user in runtime (hence, that's why I can't simply set an LP from the beginning). – Alvaro Palma Aste Jan 12 '22 at 19:44
  • Another option you could try (I'm not 100% sure if this will work, but it should) is to add an event handler that gets called after the lp solve and that just calls `SCIPinterruptSolve`. – Leon Jan 13 '22 at 20:49
  • I assume you're proposing to solve the original MIP, and place on it the handler that is going to be triggered once the LP part of it has finished. If that's the case, how could I know that the LP portion of the problem finished? – Alvaro Palma Aste Jan 16 '22 at 02:30
  • Event handlers have a timing. So you would use `SCIP_EVENTTYPE_FIRSTLPSOLVED` and that means your event handler gets called right after the first LP is solved. Then you just add an EventhdlrData that has a switch if you already called the stop once and also a parameter that deterimines if you should interrupt at all (turn it off if you directly want to solve the MIP) – Leon Jan 17 '22 at 08:41