Pyomo parameter and variable encoding

Viewed 114

I have a set of parameters and variables to encode:

The parameters are:

I Set of boxes to be packed
J Set of available ULDs
li × wi × hi Length × width × height of box i for i ∈ I
vi/ci Volume/Weight of box i for i ∈ I
Li × Wi × Hi Length × width × height of ULD j for j ∈ J
Cj Maximum gross weight of ULD j for j ∈ J
Vj Volume of ULD j for j ∈ J

And the variables:

pij for i ∈ I, j ∈ J (a boolean)
uj for j e J (a boolean)
(xi, yi, zi) for i ∈ I
(dxi, dyi, dzi) for i ∈ I
r_iab for i ∈ I (a boolean)
x^p_ik for i ∈ I (a boolean)
y^p_ik for i ∈ I (a boolean)
z^p_ik for i ∈ I (a boolean)

where a, b ∈ {1, 2, 3}

And this is my code:

import pyomo.environ as pyo
import pandas as pd
model = pyo.AbstractModel()

model.I = pyo.Param(within=pyo.NonNegativeIntegers) # set of boxes to be packed
model.J = pyo.Param(within=pyo.NonNegativeIntegers) # set of availabel ULDs

model.i = pyo.RangeSet(1, model.I) 
model.i_copy = pyo.RangeSet(1, model.I) 
model.j = pyo.RangeSet(1, model.J)

model.li = pyo.Param(model.i) # length of box
model.wi = pyo.Param(model.i) # width  of box
model.hi = pyo.Param(model.i) # height of box

# model.vi = 
model.ci = pyo.Param(model.i) # weight of box 

model.Lj = pyo.Param(model.j) # length of pallet
model.Wj = pyo.Param(model.j) # width  of pallet
model.Hj = pyo.Param(model.j) # height of pallet

# model.Vj = 
model.Cj = pyo.Param(model.j) # weight of pallet

model.pij = pyo.Var(model.i, model.j, domain=pyo.Boolean)
model.uj = pyo.Var(model.j, domain=pyo.Boolean)
model.xi = pyo.Var(model.i, domain=pyo.NonNegativeIntegers)
model.yi = pyo.Var(model.i, domain=pyo.NonNegativeIntegers)
model.zi = pyo.Var(model.i, domain=pyo.NonNegativeIntegers)

model.dxi = pyo.Var(model.i, domain=pyo.NonNegativeIntegers)
model.dyi = pyo.Var(model.i, domain=pyo.NonNegativeIntegers)
model.dzi = pyo.Var(model.i, domain=pyo.NonNegativeIntegers)

model.A = pyo.RangeSet(3)
model.B = pyo.RangeSet(3)
model.r = pyo.Var(model.i, model.A, model.B, within=pyo.Boolean)

model.xik = pyo.Var(model.i, model.i_copy, within=pyo.Boolean)
model.yik = pyo.Var(model.i, model.i_copy, within=pyo.Boolean)
model.zik = pyo.Var(model.i, model.i_copy, within=pyo.Boolean)

However, I know for sure that it's wrong because after I added the constraints it did not work as intended. The input datasets are following this format:

    pallet_id   width   length  height  max_weight
1   pallet_1    100 100 100 10
2   pallet_2    100 100 100 10

    box_id  description width   length  height  weight  fragility
1   box_1   Food    45.7    45.7    45.7    1   0
2   box_2   Food    45.7    45.7    45.7    1   2
3   box_3   Food    45.7    45.7    45.7    1   0
4   box_6   Food    45.7    45.7    45.7    1   1
5   box_10  Pharma  54  33.5    19.5    1   1
6   box_11  Food    45.7    45.7    45.7    1   0
7   box_12  Food    45.7    45.7    45.7    1   0
8   box_20  Food    55  34  14.5    1   1

So basically, I don't know if it should be an AbstractModel or a ConcreteModel, neither the real meaning of Set (I tried reading the docs and checked some examples, but I didn't understand it clearly for this problem.)

1 Answers

Here's a few ideas.

First, do a ConcreteModel for certain. Python is so good at hauling in data, that I cannot think of a reason to use Abstract Model unless for backward compatibility. You can pull data from your data frames, or a .csv, or dictionaries, etc. with just pure python.

If you use a data frame from pandas, get away from the pandas quickly and put the data into the model. It is then easy/best to keep pprint()-ing your model while you build it to look for errors, and as long as you stuff in the sets, params, etc. into the model it is usually fairly clear.

If you use a data frame, select an index (either the default, or something unique to use as the index set. Then, when you pull out the data for params, it will be indexed by the same stuff in the series that pandas gives you. (see code).

I don't know why you were copying your Set, but there should never be a need for that. You were using RangeSet too, which seems unnecessary--it just gives you a range list. I'd either use the default index of the frame, or ID like I show below--if it is unique.

Bottom line: Build a little, and use pprint to QA, then build a little more.

If you wanted to get fancy (after you get something working) you could also put your dimensions into tuples or NamedTuples (l, w, h, wt) and index them, but it is just as clear (and probably easier) to handle them individually like you are.

Try something like this and similar for your pallets or "ULD" ??

I smashed your box data frame into a file to read it back in, unaltered.

Code:

import pandas as pd
import pyomo.environ as pyo

# read in the data.  
df = pd.read_table('box_data.txt', header=0, delim_whitespace=True)

print('dataframe:')
print(df)
print()

# note that this DF has a numerical index.  We need/want? the data to be indexed by
# 'box_id' which needs to be unique to make it a valid index for what we want to do

assert df.box_id.is_unique, 'the index set is not unique....  stop and investigate'

df = df.set_index('box_id')

# now we can peel out the index for our set and use the columns (pd.Series) to pull
# in the parameters.  For example, this series is a key-value pairing

print(df['width'])

# set up the model

model = pyo.ConcreteModel()

model.B = pyo.Set(initialize = df.index)  # Box Index

model.box_length =  pyo.Param(model.B, initialize=df['length'].to_dict())
model.box_width =   pyo.Param(model.B, initialize=df['width'].to_dict())


model.pprint()

Yields:

dataframe:
   box_id description  width  length  height  weight  fragility
0   box_1        Food   45.7    45.7    45.7       1          0
1   box_2        Food   45.7    45.7    45.7       1          2
2   box_3        Food   45.7    45.7    45.7       1          0
3   box_6        Food   45.7    45.7    45.7       1          1
4  box_10      Pharma   54.0    33.5    19.5       1          1
5  box_11        Food   45.7    45.7    45.7       1          0
6  box_12        Food   45.7    45.7    45.7       1          0
7  box_20        Food   55.0    34.0    14.5       1          1

box_id
box_1     45.7
box_2     45.7
box_3     45.7
box_6     45.7
box_10    54.0
box_11    45.7
box_12    45.7
box_20    55.0

Name: width, dtype: float64
1 Set Declarations
    B : Size=1, Index=None, Ordered=Insertion
        Key  : Dimen : Domain : Size : Members
        None :     1 :    Any :    8 : {'box_1', 'box_2', 'box_3', 'box_6', 'box_10', 'box_11', 'box_12', 'box_20'}

2 Param Declarations
    box_length : Size=8, Index=B, Domain=Any, Default=None, Mutable=False
        Key    : Value
         box_1 :  45.7
        box_10 :  33.5
        box_11 :  45.7
        box_12 :  45.7
         box_2 :  45.7
        box_20 :  34.0
         box_3 :  45.7
         box_6 :  45.7
    box_width : Size=8, Index=B, Domain=Any, Default=None, Mutable=False
        Key    : Value
         box_1 :  45.7
        box_10 :  54.0
        box_11 :  45.7
        box_12 :  45.7
         box_2 :  45.7
        box_20 :  55.0
         box_3 :  45.7
         box_6 :  45.7
Related