Reputation: 1011
I am new to operations research, so would really appreciate your help. I don't know if this is possible to do within the OR framework, but here's the problem. Suppose I have a set of SKUs, their regular prices, their price elasticities, their current demand in units/week and their inventory levels . If, after N weeks on regular prices, the inventory levels at still at least 50% of starting, the prices are allowed to go down between 10% and 15%. The goal is to optimize revenue and minimize left-over inventory by M weeks.
I've tried setting it up like so
from pyomo.environ import *
sku_data = {
'SKU1': {'current_price': 100, 'price_elasticity': -0.2, 'current_demand': 50, 'current_inventory': 1000},
'SKU2': {'current_price': 150, 'price_elasticity': -0.3, 'current_demand': 40, 'current_inventory': 1200},
}
# Pyomo model
model = ConcreteModel()
# Sets
model.SKUs = Set(initialize=sku_data.keys())
model.Weeks = RangeSet(1, 12) # 8 weeks in total (4 regular, 4 promo)
# Parameters
model.current_price = Param(model.SKUs, initialize=lambda model, sku: sku_data[sku]['current_price'])
model.price_elasticity = Param(model.SKUs, initialize=lambda model, sku: sku_data[sku]['price_elasticity'])
model.current_demand = Param(model.SKUs, initialize=lambda model, sku: sku_data[sku]['current_demand'])
model.current_inventory = Param(model.SKUs, initialize=lambda model, sku: sku_data[sku]['current_inventory'])
# Variables
model.regular_price = Var(model.SKUs, within=NonNegativeReals, bounds=(0, None),
initialize=lambda model, sku: sku_data[sku]['current_price'])
model.promo_price_discount = Var(model.SKUs, within=NonNegativeReals, bounds=(0, 0.5),
initialize=0.2) # Assume max discount is 50%
model.promo_price = Var(model.SKUs, within=NonNegativeReals, bounds=(0, None),
initialize=lambda model, sku: model.regular_price[sku] * (1 - model.promo_price_discount[sku]))
model.regular_duration = Var(model.SKUs, within=NonNegativeIntegers, bounds=(4, None),
initialize=4)
model.promo_duration = Var(model.SKUs, within=NonNegativeIntegers, bounds=(0, None),
initialize=0)
def calculate_demand(base_price, new_price, elasticity, base_demand):
return base_demand*(1 + elasticity * (new_price - base_price)/base_price)
def calculate_inventory(base_inventory, base_demand, elasticity,
regular_duration, promo_duration, regular_price, promo_price):
inventory = base_inventory
for t in range(1, 12): # Assuming 8 weeks in total (adjust as needed)
if t <= regular_duration:
inventory -= calculate_demand(regular_price, regular_price, elasticity, base_demand)*regular_duration
elif t <= regular_duration + promo_duration:
inventory -= calculate_demand(regular_price, promo_price, elasticity, base_demand)*promo_duration
return inventory
# Objective 1 is maximizing revenue
model.obj = Objective(expr=sum(
(calculate_demand(
model.current_price[sku], model.regular_price[sku],
model.price_elasticity[sku], model.current_demand[sku]
) * model.regular_price[sku]* model.regular_duration[sku] +
calculate_demand(
model.current_price[sku], model.promo_price[sku],
model.price_elasticity[sku], model.current_demand[sku]
) * model.promo_price[sku] * model.promo_duration[sku])
for sku in model.SKUs
), sense=maximize)
def inventory_constraint_rule(model, sku):
base_inventory = model.current_inventory[sku]
base_demand = model.current_demand[sku]
regular_duration = value(model.regular_duration[sku])
elasticity = model.price_elasticity[sku]
promo_duration = value(model.promo_duration[sku])
regular_price = model.regular_price[sku]
promo_price = model.promo_price[sku]
return calculate_inventory(base_inventory, base_demand, elasticity,
regular_duration, promo_duration, regular_price, promo_price) >= 0
model.inventory_con = Constraint(model.SKUs, rule=inventory_constraint_rule)
def promo_switch_constraint_rule(model, sku):
base_inventory = model.current_inventory[sku]
base_demand = model.current_demand[sku]
regular_duration = value(model.regular_duration[sku])
elasticity = model.price_elasticity[sku]
promo_duration = value(model.promo_duration[sku])
regular_price = model.regular_price[sku]
promo_price = model.promo_price[sku]
inventory_after_regular = calculate_inventory(base_inventory, base_demand, elasticity,
4, 0, regular_price,
promo_price)
# Check if promo switch is needed and set regular_duration accordingly
return inventory_after_regular >= 0.5 * base_inventory
model.promo_switch_con = Constraint(model.SKUs, rule=promo_switch_constraint_rule)
# Solve the optimization problem
solver = SolverFactory('ipopt') # Use an appropriate solver (e.g., 'glpk' or 'cbc')
solver.solve(model, tee=True)
# Display results
for sku in model.SKUs:
print(f"SKU: {sku}")
print(f"Optimal Regular Price: {value(model.regular_price[sku])}")
print(f"Optimal Promo Price: {value(model.promo_price[sku])}")
print(f"Optimal Promo Price Discount: {value(model.promo_price_discount[sku])}")
print(f"Optimal Regular Duration: {value(model.regular_duration[sku])}")
print()
# Access the optimal objective value
optimal_revenue = value(model.obj)
print(f"Optimal Revenue: {optimal_revenue}")
But it did not work at all, I got that the problem is 'infeasible'. I think I may have just set it up wrong though. Any pointers would be greatly appreciated!
Upvotes: 1
Views: 34