Decision Optimization

Decision Optimization

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

 View Only
  • 1.  How to obtain whole range of solutions with CPLEX optimizer solution pool

    Posted Wed October 20, 2021 11:03 AM
    I am using the CPLEX optimizer with Python to generate a pool of solutions that fulfill certain constraints. My problem is that at the moment, I can only tune the model so that I obtain a number of solutions that are closer to the optimal solution, and hence omitting the worst solutions. Now, I would like to make sure that the solution output includes the full range of solutions, so that I obtain both the best and the worst, including "all" solutions in between. Although in order to avoid the solution pool being too big, I would like the solutions to be defined by a set of steps, e.g. "find all solutions from 0 to 200 in steps of 5", generating something like:
    solution 1: 0
    solution 2: 5
    solution 3: 10
    ...
    solution X: 200

    Is there a way to do this with the current parameters for CPLEX? Or is there a way that I can set solutions "steps" in the model?

    Thanks!

    ------------------------------
    Caroline Gebara
    ------------------------------

    #DecisionOptimization


  • 2.  RE: How to obtain whole range of solutions with CPLEX optimizer solution pool

    Posted Wed October 20, 2021 11:24 AM
    The CPLEX user guide has a section on how to enumerate "all" solutions in the solution pool using the populate method. If that uses up too much memory in your case, you can try adding an incumbent callback and having the callback decide whether each newly discovered solution gets added to the pool or not. Bear in mind that, separate from memory considerations, this could take considerable time.

    The only way I know to guide CPLEX specifically to suboptimal solutions with objective values in a certain range is to add a constraint limiting the objective value to a given range. Unfortunately, each time you change the range in that constraint CPLEX will discard the previous solution tree and start over, so this could be rather time-consuming.

    ------------------------------
    Paul Rubin
    Professor Emeritus
    Michigan State University
    ------------------------------



  • 3.  RE: How to obtain whole range of solutions with CPLEX optimizer solution pool

    Posted Thu October 21, 2021 02:53 AM
    Hi

    within https://www.linkedin.com/pulse/making-optimization-simple-python-alex-fleischer/

    2 examples could help you:

    https://github.com/AlexFleischerParis/zoodocplex/blob/master/zooenumeratewithoutsolutionpool.py

    from docplex.mp.model import Model
    
    
    mdl = Model(name='buses')
    nbbus40 = mdl.integer_var(name='nbBus40')
    nbbus30 = mdl.integer_var(name='nbBus30')
    mdl.add_constraint(nbbus40*40 + nbbus30*30 >= 300, 'kids')
    mdl.minimize(nbbus40**2*500 + nbbus30**2*400)
    
    nb_iter=5
    
    for iter in range(0,nb_iter):
        mdl.solve()
        nbbus40sol=int(nbbus40.solution_value)
        nbbus30sol=int(nbbus30.solution_value)
        print(int(nbbus40sol)," buses 40 seats")
        print(int(nbbus30sol)," buses 30 seats")
        print("cost : ",mdl.objective_value)
        print()
        mdl.add_constraint(mdl.logical_or((nbbus40sol!=nbbus40),
                nbbus30sol!=nbbus30))​


    and

    https://github.com/AlexFleischerParis/zoodocplex/blob/master/zooenumerateall.py

    from docplex.mp.model import Model
    
    from cplex.exceptions import CplexError, CplexSolverError
    
    def make_bus_model(**kwargs):
        mdl = Model(name='buses', **kwargs)
        nbbus40 = mdl.integer_var(name='nbBus40')
        nbbus30 = mdl.integer_var(name='nbBus30')
        mdl.add_constraint(nbbus40*40 + nbbus30*30 >= 300, 'kids')
        mdl.minimize(nbbus40*400 + nbbus30*300)
        mdl.parameters.mip.pool.intensity=4
        mdl.parameters.mip.pool.absgap = 0
        mdl.apply_parameters()
        return mdl
    
    
    def generate_soln_pool(mdl):
        cpx = mdl.get_cplex()
        try:
            cpx.populate_solution_pool()
        except CplexSolverError:
            print("Exception raised during populate")
            return []
        numsol = cpx.solution.pool.get_num()
        print("The solution pool contains %d solutions." % numsol)
        meanobjval = cpx.solution.pool.get_mean_objective_value()
        print("The average objective value of the solutions is %.10g." %
              meanobjval)
    
        nb_vars = mdl.number_of_variables
        sol_pool = []
        for i in range(numsol):
            objval_i = cpx.solution.pool.get_objective_value(i)
    
            x_i = cpx.solution.pool.get_values(i)
            assert len(x_i) == nb_vars
            sol = mdl.new_solution()
            for k in range(nb_vars):
                vk = mdl.get_var_by_index(k)
                if (x_i[k]!=0):
                    sol.add_var_value(vk, x_i[k])
            sol_pool.append(sol)
        return sol_pool
    
    if __name__ == "__main__":
        bm = make_bus_model()
        pool = generate_soln_pool(bm)
        for s, sol in enumerate(pool,start=1):
            print(" this is solution #{0} of the pool".format(s))
            sol.display()


    with the first way, when you solve again you can ask the objective to be at least 5 different from the previous one

    regards



    ------------------------------
    [Alex] [Fleischer]
    [EMEA CPLEX Optimization Technical Sales]
    [IBM]
    ------------------------------



  • 4.  RE: How to obtain whole range of solutions with CPLEX optimizer solution pool

    Posted Sun October 24, 2021 08:25 AM
    Edited by System Admin Fri January 20, 2023 04:25 PM
    Hi both

    Thanks for you answers! I think the incumbent call is what I need to do, if I can defined which solutions to include. However, I cannot find any good explanation on how to imply this in Python. Do you know where I can find this? or even better an example?
    I have also added my code here (without the input files), if it helps clarifying my needs, some of it a lot similar to your codes Alex. although, I don't know if the code I have now is the most optimal, since I'm definitely not an expert. So any help on how to add the callback or improve my code, would make me happy. 
    Thanks again 

    def build_diet_model(name='diet', **kwargs):
        ints = kwargs.pop('ints', False)
    
        # Create tuples with named fields for foods and nutrients
        foods = [Food(*f) for f in FOODS]
        nutrients = [Nutrient(*row) for row in NUTRIENTS]
    
        food_nutrients = {(fn[0], nutrients[n].name):
                          fn[1 + n] for fn in FOOD_NUTRIENTS for n in range(len(NUTRIENTS))}
    
        # Model
        mdl = Model(name=name, **kwargs)
    
        # Decision variables, limited to be >= Food.qmin and <= Food.qmax
        ftype = mdl.integer_vartype if ints else mdl.continuous_vartype
        qty = mdl.var_dict(foods, ftype, lb=lambda f: f.qmin,ub=lambda f: f.qmax, name=lambda f: "q_%s" % f.name)
    
        # Limit range of nutrients, and mark them as KPIs
        for n in nutrients:
            amount = mdl.sum(qty[f] * food_nutrients[f.name, n.name] for f in foods)
            mdl.add_range(n.qmin, amount, n.qmax)
            mdl.add_kpi(amount, publish_name="Total %s" % n.name)
    
        # Minimize the CO2 emitted
        total_cost = mdl.sum(qty[f] * f.unit_cost for f in foods)
        mdl.add_kpi(total_cost, 'Total cost')
    
        mdl.minimize(total_cost)
        
        mdl.parameters.mip.pool.intensity= 4
        mdl.parameters.mip.pool.absgap = 16
        mdl.parameters.mip.limits.populate.set(100000) 
        #mdl.parameters.mip.pool.capacity.set(1000)
        mdl.apply_parameters()
    
        return mdl
    
    # ----------------------------------------------------------------------------
    # Solve the model and display the result
    # ----------------------------------------------------------------------------
    
    if __name__ == '__main__':
        mdl = build_diet_model(ints=True, log_output=True, float_precision=6)
        mdl.print_information()
    
        s = mdl.solve()
        if s:
            qty_vars = mdl.find_matching_vars(pattern="q_")
            for fv in qty_vars:
                food_name = fv.name[2:]
                print("Buy {0:<25} = {1:9.6g}".format(food_name, fv.solution_value))
     
            mdl.report_kpis()
            # Save the CPLEX solution as "solution.json" program output
            with get_environment().get_output_stream("solution.json") as fp:
                mdl.solution.export(fp, "json")
        else:
            print("* model has no solution")
    
    def soln_pool(mdl):
         cpx = mdl.get_cplex()
         cpx.parameters.mip.pool.intensity.set(4)
         cpx.parameters.mip.pool.absgap.set(16)
         #cpx.parameters.mip.pool.relgap.set(0.0)
         cpx.parameters.mip.limits.populate.set(100000)
         #cpx.parameters.mip.pool.capacity.set(1000)
    
         try:
             cpx.populate_solution_pool()
         except CplexSolverError:
            print("Exception raised during populate")
            return []          
         numsol = cpx.solution.pool.get_num()
         print("The solution pool contains %d solutions." % numsol)
         meanobjval = cpx.solution.pool.get_mean_objective_value()
         print("The average objective value of the solutions is %.10g." % meanobjval)
         
         nb_vars = mdl.number_of_variables
         
         sol_pool = []
    
         for i in range(numsol):
            x_i = cpx.solution.pool.get_values(i)
            assert len(x_i) == nb_vars
            
            sol = []
            for k in range(nb_vars):
                sol.append(x_i[k])
            sol_pool.append(sol) 
         return sol_pool
    
    results = soln_pool(mdl)
    label=data.index
    matrix_results=pd.DataFrame()
        
    for s, sol in enumerate(results,start =1):
        matrix_results[str(s)]=sol
        matrix_results.index=data.index​


    ------------------------------
    Caroline Gebara
    ------------------------------



  • 5.  RE: How to obtain whole range of solutions with CPLEX optimizer solution pool

    Posted Sun October 24, 2021 09:28 AM
    Sorry, I'm not a Python user. Hopefully someone else can point you to an example.

    ------------------------------
    Paul Rubin
    Professor Emeritus
    Michigan State University
    ------------------------------