user3490622
user3490622

Reputation: 1011

Is this possible within optimization framework?

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

Answers (0)

Related Questions