I am trying to implement a "simple" 3-echelon product allocation problem with multiple time periods. The mathematical formulation looks like below:
However, I keep running into the following Error: None: KeyError: ('Berlin', 'Wismar') ERROR: Constructing component 'obj' from data=None failed: KeyError: ('Berlin', 'Wismar')
I figured the issue is coming from one of the parameter-indexed sets ("dist[f, w]), but I am not sure how to fix it. I have spent a lot of time on StackOverflow looking for ways to fix it but to no avail. So, I decided to seek help from this community.
I have attached my model file and sample data files for convenience.
Thanks in advance.
def product_allocation_acx_whs_model(K, P, F, W, S, D, pcost, tcost, dist1, dist2, turnoverR, wcap):
model = pyo.ConcreteModel(name="Product_Allocation)")
# todo - declare model decision variables
model.x = pyo.Var(K, F, P, W, within=pyo.Reals)
model.y = pyo.Var(S, W, within=pyo.Binary)
# todo - model objective function declaration
def obj_rule(m):
return sum(
m.x[k, f, p, w] * (pcost[k, f, p] + tcost[k, p] * dist1[f, w]) for k in K for f in F for p in P for w in
W) + \
sum(D[k, s, p] * tcost[k, p] * dist2[s, w] * m.y[k, s, w] for k in K for s in S for p in P for w in W)
model.obj = pyo.Objective(rule=obj_rule, sense=minimize)
# todo - warehs_product_alloc.2 equation - double check this construction
def demand_c_rule(m, p, f, w, s):
return sum((m.x[k, p, f, w] for k in K) == sum(m.d[k, s, p] * m.y[k, s, w]) for k in K)
model.obj = pyo.Constraint(F, W, S, rule=demand_c_rule)
# warehs_product_alloc.3 equation - construct the warehouse capacity constraint
def warehouse_capacity_c_rule(m, p, w, s, d):
return sum(sum(d[k, s, p] / turnoverR[p]) <= wcap[w] for k in K)
model.wcap = pyo.Constraint(P, W, S, D, rule=warehouse_capacity_c_rule)
# warehs_product_alloc.4 equation - construct the warehouse-sales outlet association constraint
def warehouse_sales_outlet_association_rule(m, s, w):
return (m.y[k, s, w] for k in K) == 1 # if a sales outlet is associated with a warehouse
model.assoc = pyo.Constraint(S, W, rule=warehouse_sales_outlet_association_rule)
# warehs_product_alloc.5 equation - construct production non-negativity constraint
def production_non_negativity(m, p, f, w):
return sum(m.x[k, p, f, w] for k in K) >= 0 # production must be greater than zero
model.non_negativity = pyo.Constraint(P, F, W, rule=production_non_negativity)
# warehs_product_alloc.6 equation - construct binary (or hard) constraint
def binary_const(m, s, w):
for k in K:
return 0 <= m.y[k, s, w] <= 1 # associating a sales outlet to a warehouse
model.assocBounds = pyo.Constraint(S, W, rule=binary_const)
return model
# todo - dynamic spin on reading a data
def preprocessed_model_data(fpath):
df = pd.read_excel(fpath)
# time periods
# K = list(df['Periods(Days)'].map(str))
K = df['Periods(Days)'].unique().tolist()
# products
P = list(df.Products.map(str))
# factories
F = list(df.Factory.map(str))
# warehouses
W = list(df.Warehouse.map(str))
# sales outlets
S = list(df['Sales Outlets'].map(str))
# process the data file
demand, distFW, distWS, upCost, transCostp, turnOR, Wcapty, Pcapty, InvhldCst, EndInvent = {}, {}, {}, {}, {}, {}, \
{}, {}, {}, {}
for i in df.index:
keyd = (df.at[i, 'Periods(Days)'], df.at[i, 'Sales Outlets'], df.at[i, 'Warehouse'])
valued = df.at[i, 'Sales']
keydstfw = (df.at[i, 'Factory'], df.at[i, 'Warehouse'])
valuedstfw = df.at[i, 'Dist Fact-Whse']
keydstws = (df.at[i, 'Warehouse'], df.at[i, 'Sales Outlets'])
valuedstws = df.at[i, 'Dist Whse-Sales Outlet']
keyuCost = (df.at[i, 'Periods(Days)'], df.at[i, 'Factory'], df.at[i, 'Products'])
valueuCost = df.at[i, 'Unit Cost of Production']
keyTcost = (df.at[i, 'Periods(Days)'], df.at[i, 'Products'])
valueTCost = df.at[i, 'Trans Cost']
keyTOR = (df.at[i, 'Periods(Days)'], df.at[i, 'Products'], df.at[i, 'Warehouse'])
valueTOR = df.at[i, 'Turnover Rate']
keywcap = (df.at[i, 'Periods(Days)'], df.at[i, 'Warehouse'])
valuewcap = df.at[i, 'Whse Capacity']
dictionary_demand = {keyd: valued}
demand.update(dictionary_demand)
dictionary_dstfw = {keydstfw: valuedstfw}
distFW.update(dictionary_dstfw)
dictionary_dstws = {keydstws: valuedstws}
distWS.update(dictionary_dstws)
dictionary_uCost = {keyuCost: valueuCost}
upCost.update(dictionary_uCost)
dictionary_TCost = {keyTcost: valueTCost}
transCostp.update(dictionary_TCost)
dictionary_TOR = {keyTOR: valueTOR}
turnOR.update(dictionary_TOR)
dictionary_Wcap = {keywcap: valuewcap}
Wcapty.update(dictionary_Wcap)
return K, P, F, W, S, demand, upCost, transCostp, distFW, distWS, turnOR, Wcapty
# todo - create the model runner function and dataset for the model
def main():
dirpath = '../data/input/'
files_dist = \[fname for fname in os.listdir(dirpath) if fname.endswith('.xlsx') and 'testD' in fname\]
for efile in files_dist:
K, P, F, W, S, demand, upCost, transCostp, distFW, distWS, turnOR, Wcapty = preprocessed_model_data(
dirpath + efile)
# Create the Pyomo model with a call of the model function
model = product_allocation_acx_whs_model(K, P, F, W, S, demand, upCost, transCostp, distFW, distWS, turnOR,
Wcapty)
# TODO - solve model instance with local gurobi solver
opt = pyo.SolverFactory('gurobi')
res = opt.solve(model)
pyo.assert_optimal_termination(res)
# model.write('../data/output/productallocationmodel.lp')
model.display()
if __name__ == '__main__':
main()
You have several significant errors in the code. I strongly suggest a couple things:
Start out with a very small subset of your data and get that running. That way you can inspect the elements, and you can print out the model to QA it with model.pprint()
.
Forget about putting things in separate functions for now. Just do this whole thing sequentially and it will help you debug it, I think.
Do some research on how to get dictionaries out of data frames. You are doing it wrong--and it should be easy. The way you are doing it now, you are indexing through the data frame (always a sign of problems) and you are OVERWRITING the items you are putting data into each time in the loop, so they will be incorrect. Print a couple after the loop to QA. That is your missing data.
In your pyomo model, you are OVERWRITING model.obj
because you are declaring it twice. You should be getting a warning for that.
In the second (mistaken) declaration of model.obj
that is attached to demand_c_rule
your are passing in 3 parameters where the function requires 4 (+ the model).