1

I need to solve an optimization problem that is similar to the knapsack problem. I have detailed the optimization problem in this post: knapsack optimization with dynamic variables I actually need to tu use python instead of OPL, so I have installed the docplex and clpex packages in order to use the cplex optimization framework.

So here is the OPL code that I want to convert to python using docplex

{string} categories=...;
{string} groups[categories]=...;

{string} allGroups=union (c in categories) groups[c];

{string} products[allGroups]=...;
{string} allProducts=union (g in allGroups) products[g];

float prices[allProducts]=...;

int Uc[categories]=...;
float Ug[allGroups]=...;

float budget=...;

dvar boolean z[allProducts]; // product out or in ?

dexpr int xg[g in allGroups]=(1<=sum(p in products[g]) z[p]); 

dexpr int xc[c in categories]=(1<=sum(g in groups[c]) xg[g]);

maximize 
sum(c in categories) Uc[c]*xc[c]+
sum(c in categories) sum(g in groups[c]) Uc[c]*Ug[g]*xg[g];
subject to
{
ctBudget:
    sum(p in allProducts) z[p]*prices[p]<=budget;
}

{string} solution={p | p in allProducts : z[p]==1};

execute
{
writeln("solution = ",solution);
}

Here is my first code attempt:

from collections import namedtuple

from docplex.mp.model import Model


# -------------------------------------------------------------------- 

# Initialize the problem data
# --------------------------------------------------------------------

Categories_groups = {"Carbs": ["Meat","Milk"],"Protein":["Pasta","Bread"], "Fat": ["Oil","Butter"]}
Groups_Products = {"1":["Product11","Product12"], "2": ["Product21","Product22","Product23"], "3":["Product31","Product32"],"4":["Product41","Product42"], "5":["Product51"],"6":["Product61","Product62"]}

Products_Prices ={"Product11":1,"Product12":4,"Product21":1,"Product22":3,"Product23":2,"Product31":4,"Product32":2,"Product41":1,"Product42":3,"Product51":1,"Product61":2,"Product62":1}
Uc=[1,1,0];
Ug=[0.8,0.2,0.1,1,0.01,0.6];
budget=3;



def build_diet_model(**kwargs):


    allcategories = Categories_groups.keys()
    allgroups = Groups_Products.keys()
    prices=Products_Prices.values()

    # Model
    mdl = Model(name='summary', **kwargs)


    for g, products in Groups_Products.items():
        xg = mdl.sum(z[p] for p in products)# this line is not correct as I dont know how to add the condition like in the OPL code, and I was unable to model the variable z and add it as decision variable to the model.


   mdl.add_constraint(mdl.sum(Products_Prices[p] * z[p] for p in Products_Prices.keys() <= budget)
   mdl.maximize(mdl.sum(Uc[c] * xc[c] for c in Categories_groups.keys()) + 
   model.sum(xg[g] * Uc[c] * Ug[g] for c, groups in Categories_groups.items() for g in groups))
   mdl.solve()

if __name__ == '__main__':


    build_diet_model()

I actually don't know how to model correctly the variables xg, xc and z like in the OPL code?

Any ideas on how to model them correctly. Thank you in advance

EDIT: Here is the edit after @HuguesJuille suggestion, I have cleaned the code and it works correctly now.

from docplex.mp.model import Model
from docplex.util.environment import get_environment

# ----------------------------------------------------------------------------
# Initialize the problem data
# ----------------------------------------------------------------------------

Categories_groups = {"Carbs": ["Meat","Milk"],"Protein":["Pasta","Bread"], "Fat": ["Oil","Butter"]}

Groups_Products = {"Meat":["Product11","Product12"], "Milk": ["Product21","Product22","Product23"], "Pasta": ["Product31","Product32"],
                   "Bread":["Product41","Product42"], "Oil":["Product51"],"Butter":["Product61","Product62"]}
Products_Prices ={"Product11":1,"Product12":4, "Product21":1,"Product22":3,"Product23":2,"Product31":4,"Product32":2,
                    "Product41":1,"Product42":3, "Product51": 1,"Product61":2,"Product62":1}




Uc={"Carbs": 1,"Protein":1, "Fat": 0 }

Ug = {"Meat": 0.8, "Milk": 0.2, "Pasta": 0.1, "Bread": 1, "Oil": 0.01, "Butter": 0.6}
budget=3;


def build_userbasket_model(**kwargs):


    allcategories = Categories_groups.keys()

    allgroups = Groups_Products.keys()

    allproducts = Products_Prices.keys()

    # Model
    mdl = Model(name='userbasket', **kwargs)
    z = mdl.binary_var_dict(allproducts, name='z([%s])')

    xg = {g: 1 <= mdl.sum(z[p] for p in Groups_Products[g]) for g in allgroups}

    xc = {c: 1 <= mdl.sum(xg[g] for g in Categories_groups[c]) for c in allcategories}


    mdl.add_constraint(mdl.sum(Products_Prices[p] * z[p] for p in allproducts) <= budget)
    mdl.maximize(mdl.sum(Uc[c] * xc[c] for c in allcategories) + mdl.sum(
        xg[g] * Uc[c] * Ug[g] for c in allcategories for g in Categories_groups[c]))
    mdl.solve()



    return mdl

if __name__ == '__main__':
    """DOcplexcloud credentials can be specified with url and api_key in the code block below.

    Alternatively, Context.make_default_context() searches the PYTHONPATH for
    the following files:

        * cplex_config.py
        * cplex_config_<hostname>.py
        * docloud_config.py (must only contain context.solver.docloud configuration)

    These files contain the credentials and other properties. For example,
    something similar to::

       context.solver.docloud.url = "https://docloud.service.com/job_manager/rest/v1"
       context.solver.docloud.key = "example api_key"
    """
    url = None
    key = None

    mdl = build_userbasket_model()

    # will use IBM Decision Optimization on cloud.
    if not mdl.solve(url=url, key=key):
        print("*** Problem has no solution")
    else:
        mdl.float_precision = 3
        print("* model solved as function:")
        mdl.print_solution()

        # Save the CPLEX solution as "solution.json" program output
        with get_environment().get_output_stream("solution.json") as fp:
            mdl.solution.export(fp, "json")

I hope that this will help a beginner like me having the same problem.

1 Answer 1

2

If I understood correctly your data model (I'm not sure you data is consistent in your example (Categories_groups and Groups_Products don't have the same collections of values for 'groups').), the definition of your decision variables and expressions would look like this:

z = mdl.binary_var_dict(allProducts, name='z([%s])')
xg = {g: 1 <= mdl.sum(z[p] for p in Groups_Products[g]) for g in allgroups}
xc = {c: 1 <= mdl.sum(xg[g] for g in Categories_groups[c]) for c in allcategories}

Here, the 'z' decision variable is defined as a dictionary. It can then be indexed easily.

One can also find documentation about writing docplex models here: https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/mp/creating_model.html

Note that using pandas may be more efficient for defining complex slicing if you need to build models handling large datasets.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much for your answer and valuable pieces of advice. I have updated my question to add the new variables and now my code works correctly. I only have this warning "Warning: No object with index: 0" and the same warning until index 8. Do you have an idea why am having this problem? I have googled this but couldn't find any answer. Thank you in advance. Regards.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.