0
$\begingroup$

I'm working on a Python application using PuLP for optimization, and I'm having trouble ensuring that only one material is produced on a given day.

The constraint prob += pulp.lpSum(production_flag[m][d] for m in materials) <= 1 is intended to ensure that only one material is produced on a given day. However, it doesn't seem to be working as expected. Multiple materials are still being scheduled for production on the same day. How can I modify my constraints or logic to ensure that only one material is produced on any given day?

Below is my current code:

materials = ["600065", "600047", "600063", "600076", "600061"]
current_date = datetime(2024, 6, 13)
current_inventory = {
    "600065": {current_date: 247.5},
    "600047": {current_date: 0},
    "600063": {current_date: 205.2},
    "600076": {current_date: 80},
    "600061": {current_date: 1.2}
}

quantity_to_be_sold = {
    "600065": {datetime(2024, 6, 14): 25,
               datetime(2024, 6, 15): 55,
               datetime(2024, 6, 18): 25, 
               datetime(2024, 6, 20): 25, 
               datetime(2024, 6, 21): 25, 
               datetime(2024, 6, 27): 25, 
               datetime(2024, 6, 28): 25, 
               datetime(2024, 6, 30): 50},
    "600047": {datetime(2024, 6, 24): 25},
    "600063": {datetime(2024, 6, 14): 52.8, 
               datetime(2024, 6, 15): 79.2,
               datetime(2024, 6, 21): 26.4, 
               datetime(2024, 6, 22): 79.2, 
               datetime(2024, 6, 28): 52.8},
    "600076": {datetime(2024, 6, 14): 20, 
               datetime(2024, 6, 15): 20,
               datetime(2024, 6, 18): 40, 
               datetime(2024, 6, 19): 40, 
               datetime(2024, 6, 24): 20,
               datetime(2024, 6, 26): 20,
               datetime(2024, 6, 27): 20,
               datetime(2024, 6, 28): 20,
               datetime(2024, 6, 21): 20},
    "600061": {datetime(2024, 6, 21): 26.4}
}

production_capacity = {"600065": 80,"600047": 80, "600063": 36, "600076": 30, "600061": 36}
waiting_period = {"600065": 1,"600047": 1, "600063": 2, "600076": 2, "600061": 2}

#Collect all dates from input data and current inventory
all_dates = sorted(set(date for dates in quantity_to_be_sold.values() for date in dates).union(
    date for dates in current_inventory.values() for date in dates))

#Define the problem
prob = pulp.LpProblem("AS_Packaging_Optimization", pulp.LpMaximize)

#Define decision variables
production = pulp.LpVariable.dicts("Production", (materials, all_dates), 0, None, pulp.LpContinuous)
inventory = pulp.LpVariable.dicts("Sales", (materials, all_dates), 0, None, pulp.LpContinuous)
available_for_sale = pulp.LpVariable.dicts("AvailableForSale", (materials, all_dates), 0, None, pulp.LpContinuous)
production_flag = pulp.LpVariable.dicts("ProductionFlag", (materials, all_dates), 0, 1, pulp.LpBinary)

#Objective function: Maximize total sales
prob += pulp.lpSum([quantity_to_be_sold[m].get(d, 0) for m in materials for d in all_dates])

def get_cumulative_production(production_data, current_date, waiting_period, all_dates):
    cumulative_production = 0
    start_date = current_date - timedelta(days=waiting_period-1)
    
    for date in all_dates:
        if start_date <= date <= current_date and date in production_data:
           cumulative_production += production_data[date]
    
    return cumulative_production

#Constraints: Inventory
for m in materials:
    for i, d in enumerate(all_dates):
        # Inventory balance constraints
        if i == 0:
            prob += inventory[m][d] == current_inventory[m].get(d, 0)
        else:
            prev_day = all_dates[i - 1]
            prob += inventory[m][d] == inventory[m][prev_day] + production[m][d] - quantity_to_be_sold[m].get(d, 0)

        # Available for sale considering the waiting period
        if i == 0:
            prob += available_for_sale[m][d] == inventory[m][d]
        else:
            production_data = {date: production[m][date] for date in all_dates}
            total_production_deduction = get_cumulative_production(production_data, d, waiting_period[m], all_dates)
            prob += available_for_sale[m][d] == inventory[m][d] - total_production_deduction
    
#Constraint: Materials and Production on initial day 
for m in materials:
    for d in current_inventory[m]:
        prob += production[m][d] == 0

#Constraint: Materials and Production on subsequent day 
for d in all_dates:
    prob += pulp.lpSum(production_flag[m][d] for m in materials) <= 1
    for m in materials:  
        prob += production[m][d] <= production_capacity[m] * production_flag[m][d]
        prob += production[m][d] >= 0 
    
#Solve the problem
prob.solve()

# Extract results
def extract_results(material):
    results = {
        "Date": [],
        "Material": [],
        "Production": [],
        "Sales": [],
        "Inventory": [],
        "AvailableForSale": []
    }

    for d in all_dates:
        results["Date"].append(d.strftime("%Y-%m-%d"))
        results["Material"].append(material)
        results["Production"].append(pulp.value(production[material][d]))
        results["Sales"].append(quantity_to_be_sold[material].get(d, 0))
        results["Inventory"].append(pulp.value(inventory[material][d]))
        results["AvailableForSale"].append(pulp.value(available_for_sale[material][d]))

    df_results = pd.DataFrame(results)
    return df_results

# Extract results for each material
df_results_600065 = extract_results("600065")
df_results_600047 = extract_results("600047")
df_results_600063 = extract_results("600063")
df_results_600076 = extract_results("600076")
df_results_600061 = extract_results("600061")

print("Results for Material 600065:")
print(df_results_600065 if isinstance(df_results_600065, pd.DataFrame) else df_results_600065)
print("Results for Material 600047:")
print(df_results_600047 if isinstance(df_results_600047, pd.DataFrame) else df_results_600047)
print("\nResults for Material 600063:")
print(df_results_600063 if isinstance(df_results_600063, pd.DataFrame) else df_results_600063)
print("\nResults for Material 600076:")
print(df_results_600076 if isinstance(df_results_600076, pd.DataFrame) else df_results_600076)
print("\nResults for Material 600061:")
print(df_results_600061 if isinstance(df_results_600061, pd.DataFrame) else df_results_600061)
$\endgroup$
10
  • $\begingroup$ When I run your code on my machine, Pulp says that the problem is infeasible. You should first figure out why this is the case. $\endgroup$
    – PeterD
    Commented Jun 17 at 7:38
  • $\begingroup$ Hi, PeterD thank you for your comment. I run it on Jupyter Notebook and the results is feasible (I am not sure it's infeasible, but the result can expose??). Sorry, i am newbie for this thing. $\endgroup$
    – Beau Ryan
    Commented Jun 17 at 8:33
  • $\begingroup$ It might show you values for your variables, but this does not mean its feasible. In my case Pulp says that is proved to be infeasible. $\endgroup$
    – PeterD
    Commented Jun 17 at 8:47
  • $\begingroup$ Could you tell me where do you run this code? $\endgroup$
    – Beau Ryan
    Commented Jun 17 at 8:50
  • $\begingroup$ I add the extract data part after prob.solve. Could you run it to see the result? Thank you very much -/\- $\endgroup$
    – Beau Ryan
    Commented Jun 17 at 8:56

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.