Skip to main content

Gurobi apparently not respecting parameter PoolSolutions ?

Answered

Comments

7 comments

  • David Torres Sanchez
    Gurobi Staff Gurobi Staff

    Hi Alberto,

    The PoolSolutions parameter is used in conjunction with the PoolSearchMode parameter.
    As indicated in the PoolSolutions page:

    For the default value of PoolSearchMode, these are just the solutions that are found along the way in the process of exploring the MIP search tree. For other values of PoolSearchMode, this parameter sets a target for how many solutions to find, so larger values will impact performance. 

    e.g. PoolSearchMode=1 or 2.

    If you just want the first feasible solution you can use SolutionLimit.

    Cheers, 
    David

    0
  • Alberto Santini
    Gurobi-versary
    Curious
    Conversationalist

    Hi! Thanks for your answer. Unfortunately, this is not the issue because I get the same behaviour after setting \(\texttt{GRB_IntParam_PoolSearchMode}\) to 1 or 2.

    Furthermore, my interpretation of the documentation is the following:

    • With PoolSearchMode = 0, you get the best solution found plus possibly other solutions, but there is no specific guarantee of their quality. The total number of solutions is at most PoolSolutions. (This part of my interpretation is "proven false" by my example with PoolSearchMode = 0.)
    • With PoolSearchMode = 1, you get the best solution found plus at least PoolSolutions solutions. If the solution pool is smaller than PoolSolutions when optimisation is over, the solver will keep exploring the B&B tree to give you PoolSolutions solutions.
    • With PoolSearchMode = 2, you get exactly PoolSolutions solutions, and you have a guarantee that they are the best, the second-best, the third-best, ..., the PoolSolutions-th-best solutions. (This part of my interpretation is "proven false" by my new example when I set PoolSearchMode = 2.)

    Finally, just for clarity, let me state what I am trying to achieve: I would like Gurobi to return only one solution, and this solution should be optimal (or if a time-out occurs, the best one it encountered). Therefore, SolutionLimit would not work because it can return potentially sub-optimal solutions. To state it differently, I would like the solution pool to have size 1.

    Thanks again for the prompt reply!

    AS

    0
  • David Torres Sanchez
    Gurobi Staff Gurobi Staff

    Hi Alberto,

    That is weird.
    Could you share the model or a way for us to reproduce this? (what version are you using?)
    Or at least the log.

    Finally, just for clarity, let me state what I am trying to achieve: I would like Gurobi to return only one solution, and this solution should be optimal (or if a time-out occurs, the best one it encountered). Therefore, SolutionLimit would not work because it can return potentially sub-optimal solutions. To state it differently, I would like the solution pool to have size 1.

    This sounds like our default behaviour (with no additional Pool parameters).
    We will return a single solution: the optimal one if available or the best one found within the time limit.

    Cheers, 
    David

    0
  • Alberto Santini
    Gurobi-versary
    Curious
    Conversationalist

    Hi! I discovered what was happening while preparing a minimal working example for you. I was reusing the same model for multiple optimisations, and it turns out that the column pool doesn't get "cleared" unless one calls \(\texttt{m.reset(1)}\). Therefore, the sequence of events was the following:

    1. Solve the model with other parameters. It populates the solution pool with \(k\) solutions, and it is possible that \(k > 1\).
    2. Change something in the model (in my case, the objective coefficient of some variables).
    3. Set \(\texttt{PoolSolutions}\) to 1 (and \(\texttt{PoolSearchMode}\) to 2).
    4. Optimise.
    5. The model still contains \(k\) solutions in the pool, but only the first one is from the new optimisation.

    Calling \(\texttt{m.reset(1)}\) before step 4 solves the problem. I am not sure if this behaviour is a bug or by design. I was expecting that PoolSolutions = 1 should give exactly one solution, no matter what prior optimisation occurred.

    If you want to reproduce the behaviour, here is a sample LP model (although I guess any model that "visits" more than one integer feasible solution would work), and here is some code:

    #include <gurobi_c++.h>
    #include <cstdlib>
    #include <filesystem>

    int main() {
        std::filesystem::path model_file{"../gurobi.lp"};

        GRBEnv env;
        GRBModel model{env, model_file};

      // Comment this and the "bug" disappears, because it is
    // in this first optimisation that the solution pool gets
    // filled with >1 column.
        model.optimize();

        model.set(GRB_IntParam_PoolSolutions, 1);

        for(auto&& pool_search_mode : {0, 1, 2}) {
            model.set(GRB_IntParam_PoolSearchMode, pool_search_mode);

            // Aleternatively, uncomment this and the "bug" also disappears,
            // because now the column pool is cleared at each iteration.
            // model.reset(1);

            model.optimize();

            std::cout << "Status: " << model.get(GRB_IntAttr_Status) << "\n";
            std::cout << "Number of solutions in pool: " << model.get(GRB_IntAttr_SolCount) << "\n";
            std::cout << "\tPoolSolutions: " << model.get(GRB_IntParam_PoolSolutions) << "\n";
            std::cout << "\tPoolSearchMode: " << model.get(GRB_IntParam_PoolSearchMode) << "\n";
        }

        return EXIT_SUCCESS;
    }

    Best,

    AS

    0
  • David Torres Sanchez
    Gurobi Staff Gurobi Staff

    Hi Alberto,

    Thanks for the very clear explanation and code.
    I think this is somehow expected, however, I will raise this to the developers to verify.

    Cheers, 
    David

    0
  • Marika Karbstein
    Gurobi Staff Gurobi Staff

    Hi Alberto,

    We discussed this with our developers.
    If you optimize the exact same model without a reset, the data structures are reused, and changing the PoolSolutions parameter has no effect. As you observed, calling model.reset() (it is also sufficient to use default parameter of 0 for this call), forces the rebuild of the data structure and the new value of PoolSolutions is considered. So this is intended and the code you shared is an example.

    However, if you reuse the same model object but change something in the model, for example, the objective coefficients of some variables, a reset should be triggered automatically and the new PoolSolutions parameter should be considered.
    Could you please check if you have an example that fulfills the sequence of events you mentioned in your last comment (where in step 2 indeed something is changed)?

    Thanks,
    Marika

    0
  • Alberto Santini
    Gurobi-versary
    Curious
    Conversationalist

    Hi Marika! Thanks for your answer. I think the following code confirms what you have just written:

    #include <gurobi_c++.h>
    #include <cstdlib>
    #include <filesystem>

    int main() {
        std::filesystem::path model_file{"../gurobi.lp"};

        GRBEnv env{true};
        env.set(GRB_IntParam_OutputFlag, 0);
        env.set(GRB_IntParam_LogToConsole, 0);
        env.start();

        GRBModel model{env, model_file};

        model.optimize();

        GRBVar* variables = model.getVars();
        variables[0].set(GRB_DoubleAttr_UB, 0.0);

        model.set(GRB_IntParam_PoolSolutions, 1);

        for(auto&& pool_search_mode : {0, 1, 2}) {
            model.set(GRB_IntParam_PoolSearchMode, pool_search_mode);
            model.optimize();

            std::cout << "Status: " << model.get(GRB_IntAttr_Status) << "\n";
            std::cout << "Number of solutions in pool: " << model.get(GRB_IntAttr_SolCount) << "\n";
            std::cout << "\tPoolSolutions: " << model.get(GRB_IntParam_PoolSolutions) << "\n";
            std::cout << "\tPoolSearchMode: " << model.get(GRB_IntParam_PoolSearchMode) << "\n";
        }

        delete variables;

        return EXIT_SUCCESS;
    }

    Here I always get 1 column in the pool. In my production code, what was happening was probably that the new objective coefficients happened to be equal to the old ones, so an update of the data structures you mentioned was not triggered. Indeed, changing line \(\texttt{variables[0].set(GRB_DoubleAttr_UB, 0.0);}\) with \(\texttt{variables[0].set(GRB_DoubleAttr_UB, variables[0].get(GRB_DoubleAttr_UB));}\) gets me three columns in the pool instead of one.

    Thanks,

    AS

     

    0

Please sign in to leave a comment.