Search code examples
pyomo

Pyomo ERROR: Constructing component 'obj' from data=None failed: KeyError: ('Berlin', 'Wismar')


I am trying to implement a "simple" 3-echelon product allocation problem with multiple time periods. The mathematical formulation looks like below: enter image description here

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()

Solution

  • You have several significant errors in the code. I strongly suggest a couple things:

    1. 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().

    2. Forget about putting things in separate functions for now. Just do this whole thing sequentially and it will help you debug it, I think.

    3. 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.

    4. In your pyomo model, you are OVERWRITING model.obj because you are declaring it twice. You should be getting a warning for that.

    5. 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).