baxbear
baxbear

Reputation: 890

Pyomo AbstractModel and Python Dictionaries

I am new to Pyomo and I started with some small examples by understanding and testing them. Now I would like to rewrite a more complex Example using Python dictionaries the example I want to rewrite is from the Pyomo Gallery:

https://nbviewer.jupyter.org/github/Pyomo/PyomoGallery/blob/master/diet/DietProblem.ipynb

As example on how to use Python Dictionaries with pyomo I took the following:

https://pyomo.readthedocs.io/en/stable/working_abstractmodels/data/raw_dicts.html

Since two days since I started I am circling dictionaries around shifting indexes and hoping for my reimplementation to work and I just don't know what the correct approach is. Why the current approach doesn't work. Sometimes I change something about the dictionary structure and receive a different error message, then I proceed with the next one and (solve it, or just change it) and the old one pops up again.

I just will provide my current status and would appreciate your help. A working solution would probably help me a lot to understand how to model data with Python dictionaries.

from __future__ import division

import pyomo.environ as pyo
from pyomo.opt import SolverFactory
from math import inf

food_keys = ['Cheeseburger', 'Ham Sandwich', 'Hamburger', 'Fish Sandwich', 'Chicken Sandwich', 'Fries', 'Sausage Biscuit', 'Lowfat Milk', 'Orange Juice']

cost_vol_keys = ['c', 'V']

cost_vol = [[1.84, 4.0], [2.19, 7.5], [1.84, 3.5], [1.44, 5.0], [2.29, 7.3], [.77, 2.6], [1.29, 4.1], [.60, 8.0], [.72, 12.0]]

F = {food_keys[i]: 
        {cost_vol_keys[j]: 
            cost_vol[i][j] 
            for j in range(len(cost_vol_keys))} 
        for i in range(len(food_keys))}

nutrition_keys = ['Cal', 'Carbo Protein', 'VitA', 'VitC', 'Calc', 'Iron']

nutrition_req_keys = ['Nmin', 'Nmax']

nutrition_req = [
        [2000, None],
        [ 350,  375],
        [  55, None],
        [ 100, None],
        [ 100, None],
        [ 100, None],
        [ 100, None]
        ]

N = {nutrition_keys[i]: 
        {nutrition_req_keys[j]: 
            nutrition_req[i][j] 
            for j in range(len(nutrition_req_keys))} 
        for i in range(len(nutrition_keys))}

Vmax = 75.0

nutrition = [
    [510, 34, 28, 15,   6, 30, 20],
    [370, 35, 24, 15,  10, 20, 20],
    [500, 42, 25,  6,   2, 25, 20],
    [370, 38, 14,  2,   0, 15, 10],
    [400, 42, 31,  8,  15, 15,  8],
    [220, 26,  3,  0,  15,  0,  2],
    [345, 27, 15,  4,   0, 20, 15],
    [110, 12,  9, 10,   4, 30,  0],
    [ 80, 20,  1,  2, 120,  2,  2]
    ]

a = {(food_keys[i], nutrition_keys[j]): nutrition[i][j] 
        for j in range(len(nutrition_keys)) 
            for i in range(len(food_keys))}

data = {None: {
    'F': {None: F}, 
    'N': {None: N}, 
    'a': a,
    'Vmax': {None: Vmax},
    }}

model = pyo.AbstractModel()

solver          = 'gurobi'
solver_io       = 'python'
stream_solver   = False     # True prints solver output to screen
keepfiles       = False     # True prints intermediate file names (.nl,.sol,...)

opt = SolverFactory(solver, solver_io = solver_io)
opt.options['outlev'] = 1 # tell gurobi to be verbose with output
opt.options['solnsens'] = 1
opt.options['bestbound'] = 1

# Foods
model.F = pyo.Set()
# Nutrients
model.N = pyo.Set()

# Cost of each food
model.c    = pyo.Param(model.F, within = pyo.PositiveReals)
# Amount of nutrient in each food
model.a    = pyo.Param(model.F, model.N, within = pyo.NonNegativeReals)
# Lower and upper bound on each nutrient
model.Nmin = pyo.Param(model.N, within = pyo.NonNegativeReals, default = 0.0)
model.Nmax = pyo.Param(model.N, within = pyo.NonNegativeReals, default = inf)
# Volume per serving of food
model.V    = pyo.Param(model.F, within = pyo.PositiveReals)
# Maximum volume of food consumed
model.Vmax = pyo.Param(within = pyo.PositiveReals)

# Number of servings consumed of each food
model.x = pyo.Var(model.F, within = pyo.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 = pyo.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 pyo.inequality(model.Nmin[j], value, model.Nmax[j])
model.nutrient_limit = pyo.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 = pyo.Constraint(rule = volume_rule)

model_instance = model.create_instance(data)

model_instance.pprint()

results = opt.solve(model_instance, keepfiles = keepfiles, tee = stream_solver)

Upvotes: 0

Views: 851

Answers (1)

AirSquid
AirSquid

Reputation: 11938

You are running into problems here because you are asking pyomo to read your dictionaries as if they were a .dat file like the example you reference. It can't do that. It can read multi-columnar .dat files that have multiple items indexed by the same keyset, but (to my knowledge) cannot do the same thing out of a python dictionary. So you have a couple choices...

  1. Break up your dictionaries and have individual ones for cost, value, etc. This should be a trivial fix and then update your data model.

  2. Just stuff your data into a separate .dat file like the example you reference, which is perfectly fine

  3. Shift gears to a ConcreteModel() and forget about keeping the model and data separate. Python is so good at wrangling files and data tables, that I almost always elect to let python do the wrangling and then just make a ConcreteModel() from the dictionaries created. Without changing any of your dictionaries, you could do something like this:

model = pyo.ConcreteModel('chow time')
# Foods
model.F = pyo.Set(initialize=F.keys())
# Nutrients
model.N = pyo.Set(initialize=N.keys())

# Cost of each food
model.c    = pyo.Param(model.F, within = pyo.PositiveReals, initialize = {k:v['c'] for k,v in F.items()})
# Volume per serving of food
model.V    = pyo.Param(model.F, within = pyo.PositiveReals, initialize = {k:v['V'] for k,v in F.items()})

Upvotes: 1

Related Questions