How to Extract Results from an Abstract Python/Pyomo Model and Save to Dictionaries, DataFrames and Excel or csv Files

Python Pyomo Abstract Models, Results Viewing, Results Extraction, Results Saving, Python Dictionaries, Python Pandas DataFrames, Excel.xlsx CSV.csv.

 

It’s been some time since we posted on installing Python and its associated linear programming package Pyomo, followed by a few basic operations – December 2019 to be exact. These were our first two real posts on the blog.

 

Our minds turned back towards this topic when during last week, in out day job, we had to save the output from a Pyomo abstract model that we had constructed some time ago. In the end it was a case that Pyomo had at some stage updated its commands to create an instance of an abstract model from instance = model.create(data) to instance = model.create_instance(data).

 

We admit, we had to Google/Startpage it.

 

With most things Python a simple web search will pull up all of the code that you will ever need. However, in this case there wasn’t a lot that instantly appeared on how to extract results from an Pyomo abstract model. So we thought we’d outline how an abstract model user can export results from Pyomo into standard Python/pandas data structures, such as dictionariesPandas DataFrames, and Excel or csv files.

 

To illustrate how to extract and save data we will use the well-known “Diet Problem“, and credit the code we use here to user whart222 on GitHub. This model solves for the least-cost combination of foods are chosen subject to calorie and nutritional constraints.

 

The model diet.py and data diet.dat are available from Github, but Practical Economics’ adaptation of code and data are reproduced at the end of this post.

 

For those not familiar with concrete and abstract models, according to Pyomo – Optimization Modelling in Python (p35):

      • A concrete model can be used when data is available before model components are declared; while

      • An abstract model is declared first, and component construction is delayed until the data is loaded and Pyomo creates the model instance.

    If a concrete model has two variables x1 and x2, remembering that Pyomo solves for the optimal quantities of different variables subject to a series of constraints populated by user-defined parameters, a constraint defined in Pyomo might be 3*x1 + 2*x2 =< 10.

     

    An equivalent abstract model will specify the same constraint P1*x1 + P2*x2 =< P3, and then read the values of parameters P1, P2 and P3 from data files when the model is run and an instance is created.

     

    Abstract models have the advantage of being able to use the same model code for different situations. For example, our Diet Problem model could be used equally on the calorie requirements of a bodybuilder attempting to put on muscle or an overweight person trying to lose fat.

     

    To extract results we must first run the model, with data found and stored on a user-defined working directory. From the Diet example, the code to run the model is (note the pprint() command is commented out, but is handy if you want to see the inner workings of your model):

    data=workingDirectory+'\\diet.dat'
    opt=SolverFactory('glpk')
    instance=model.create_instance(data)
    #instance.pprint()
    opt.solve(instance)

    In this case we choose to obtain the data values of our model instance parameters from the diet.dat file and use the glpk solver (for installation see our earlier post). We create and then solve an instance of the abstract model.

     

    To extract the data into a Python dictionary, we simply use the Python ‘value’ function. For our model we have a variable called x, which contains the list of foods from which the modeler might select the foods chosen to meet calories and nutritional constraints (for the Python aficionados x is a Python class, while ‘Cheesburger’ is an instance variable).

    Consumption={}
    for f in instance.F:
        Consumption[f]=(value(instance.x[f]))

    We use a simple Python ‘for’ loop to read the instances of x defined by the set of foods F into a dictionary called Consumption.

     

    From there it is straightforward to bring the data into a DataFrame.

    df_Consumption=pd.DataFrame.from_dict(Consumption,orient='index').reset_index()
    df_Consumption.columns=[['Food Type','Quantity']]

    If we print df_Consumption we get:

      Food Type        Quantity
    0 Cheeseburger 4.0
    1 Ham Sandwich 0.0
    2 Hamburger 0.0
    3 Fish Sandwich 1.0
    4 Chicken Sandwich 0.0
    5 Fries 5.0
    6 Sausage Biscuit 0.0
    7 Lowfat Milk 4.0
    8 Orange Juice 0.0

    Cheeseburgers and fries and low fat milk all round!

     

    Sending df_Consumption to Excel and csv files can be done with a single line of code for each:

     

    df_Consumption.to_csv(workingDirectory+'\\diet_results_csv.csv')
    df_Consumption.to_excel(workingDirectory+'\\diet_results_Excel.xlsx')

    That’s it – very simple once you know how. Good luck.

     

    If you have any comments, suggestions or want further training then please contact us. This isn’t our full-time job and we generate Practical Economics content when we can, so we might not reply immediately, but we will get back to you.

     

    Alternatively sign up for our newsletter below for more great content coming soon.

     

    Model Code and Data

    from pyomo.environ import *
    import pandas as pd
    
    workingDirectory=XXXXXX #this should be a string containing directory where data file is located
    
    infinity = float('inf')
    
    model = AbstractModel()
    
    # Foods
    model.F = Set()
    # Nutrients
    model.N = Set()
    
    # Cost of each food
    model.c    = Param(model.F, within=PositiveReals)
    # Amount of nutrient in each food
    model.a    = Param(model.F, model.N, within=NonNegativeReals)
    # Lower and upper bound on each nutrient
    model.Nmin = Param(model.N, within=NonNegativeReals, default=0.0)
    model.Nmax = Param(model.N, within=NonNegativeReals, default=infinity)
    # Volume per serving of food
    model.V    = Param(model.F, within=PositiveReals)
    # Maximum volume of food consumed
    model.Vmax = Param(within=PositiveReals)
    
    # Number of servings consumed of each food
    model.x = Var(model.F, within=NonNegativeIntegers)
    
    # Minimize the cost of food that is consumed
    def cost_rule(model):
        return sum(model.c[i]*model.x[i] for i in model.F)
    model.cost = Objective(rule=cost_rule)
    
    # Limit nutrient consumption for each nutrient
    def nutrient_rule(model, j):
        value = sum(model.a[i,j]*model.x[i] for i in model.F)
        return inequality(model.Nmin[j], value, model.Nmax[j])
    model.nutrient_limit = Constraint(model.N, rule=nutrient_rule)
    
    # Limit the volume of food consumed
    def volume_rule(model):
        return sum(model.V[i]*model.x[i] for i in model.F) <= model.Vmax
    model.volume = Constraint(rule=volume_rule)
    
    
    data=workingDirectory+'\\diet.dat'
    opt=SolverFactory('glpk')
    instance=model.create_instance(data)
    #instance.pprint()
    opt.solve(instance)
    
    Consumption={}
    for f in instance.F:
        Consumption[f]=(value(instance.x[f]))
     
    df_Consumption=pd.DataFrame.from_dict(Consumption,orient='index').reset_index()

    df_Consumption.columns=[['Food Type','Quantity']]

    print(df_Consumption)

    df_Consumption.to_csv(workingDirectory+'\\diet_results_csv.csv')
    df_Consumption.to_excel(workingDirectory+'\\diet_results_Excel.xlsx')
     

    """
    Data for data.dat
    
    """
    
    """
    param:  F:                          c     V  :=
      "Cheeseburger"                 1.84   4.0  
      "Ham Sandwich"                 2.19   7.5  
      "Hamburger"                    1.84   3.5  
      "Fish Sandwich"                1.44   5.0  
      "Chicken Sandwich"             2.29   7.3  
      "Fries"                         .77   2.6  
      "Sausage Biscuit"              1.29   4.1  
      "Lowfat Milk"                   .60   8.0 
      "Orange Juice"                  .72  12.0 ;
    
    param Vmax := 75.0;
    
    param:  N:       Nmin   Nmax :=
            Cal      2000      .
            Carbo     350    375
            Protein    55      .
            VitA      100      .
            VitC      100      .
            Calc      100      .
            Iron      100      . ;
    
    param a:
                                   Cal  Carbo Protein   VitA   VitC  Calc  Iron :=
      "Cheeseburger"               510     34     28     15      6    30    20
      "Ham Sandwich"               370     35     24     15     10    20    20
      "Hamburger"                  500     42     25      6      2    25    20
      "Fish Sandwich"              370     38     14      2      0    15    10
      "Chicken Sandwich"           400     42     31      8     15    15     8
      "Fries"                      220     26      3      0     15     0     2
      "Sausage Biscuit"            345     27     15      4      0    20    15
      "Lowfat Milk"                110     12      9     10      4    30     0
      "Orange Juice"                80     20      1      2    120     2     2 ;
    """