You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

运筹学框架下SKU定价与库存优化模型构建及可行性问题求助

问题背景

作为运筹学新手,现遇到如下优化问题:
给定一组SKU的常规价格、价格弹性、周均需求及库存水平,规则为:若按常规价格销售N周后库存仍不低于初始值的50%,可将价格下调10%-15%;目标是在M周内最大化收入并最小化剩余库存。

尝试用Pyomo搭建模型后求解提示问题不可行,恳请排查并提供修正建议。

现有Pyomo模型代码
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}")
模型错误排查与修正建议

1. 库存计算函数逻辑严重错误

calculate_inventory函数中,每周扣减的需求量被错误乘以了regular_duration或promo_duration,导致库存被过度扣减(比如常规销售4周,每周扣减50*4=200,4周总共扣减800,远超实际需求)。正确逻辑应为每周扣减对应价格下的单周需求量:

def calculate_inventory(base_inventory, base_demand, elasticity,
                        regular_duration, promo_duration, regular_price, promo_price):
    inventory = base_inventory
    # 先扣减常规销售期的库存
    regular_demand = calculate_demand(regular_price, regular_price, elasticity, base_demand)
    inventory -= regular_demand * regular_duration
    # 再扣减促销期的库存
    promo_demand = calculate_demand(regular_price, promo_price, elasticity, base_demand)
    inventory -= promo_demand * promo_duration
    return inventory

同时,函数中循环11周(range(1,12))与注释的8周矛盾,直接按时长计算更简洁,避免循环错误。

2. 约束中使用value()导致无效约束

在inventory_constraint_rule和promo_switch_constraint_rule中,调用value(model.regular_duration[sku])会直接取变量的初始值而非优化过程中的变量值,导致约束无法正确关联变量,变成固定值约束,这是模型不可行的核心原因之一。必须直接使用变量构建约束表达式,不能用value():

def inventory_constraint_rule(model, sku):
    regular_demand = calculate_demand(model.current_price[sku], model.regular_price[sku],
                                      model.price_elasticity[sku], model.current_demand[sku])
    promo_demand = calculate_demand(model.current_price[sku], model.promo_price[sku],
                                    model.price_elasticity[sku], model.current_demand[sku])
    return model.current_inventory[sku] - regular_demand * model.regular_duration[sku] - promo_demand * model.promo_duration[sku] >= 0

3. 促销折扣范围未按需求约束

问题要求折扣为10%-15%,但当前promo_price_discount的 bounds 是(0,0.5),初始化值0.2也不符合要求。需修正变量定义:

model.promo_price_discount = Var(model.SKUs, within=NonNegativeReals, bounds=(0.1, 0.15),
                                 initialize=0.12)

同时,promo_price可以直接用表达式定义为衍生变量,避免冗余:

model.promo_price = Expression(model.SKUs, rule=lambda model, sku: model.regular_price[sku] * (1 - model.promo_price_discount[sku]))

4. 时间总长度约束缺失

问题要求在M周内完成销售,但当前模型未限制regular_duration + promo_duration <= M(比如M=12),需添加约束:

def total_duration_rule(model, sku):
    return model.regular_duration[sku] + model.promo_duration[sku] <= 12
model.total_duration_con = Constraint(model.SKUs, rule=total_duration_rule)

另外,regular_duration的bounds设置为(4, None)不合理,应上限为M:

model.regular_duration = Var(model.SKUs, within=NonNegativeIntegers, bounds=(0, 12),
                             initialize=4)

5. 目标函数未包含最小化剩余库存

原问题目标是最大化收入+最小化剩余库存,但当前仅最大化收入,需构建多目标函数(可通过加权法合并):

# 定义权重,比如收入权重0.8,剩余库存权重0.2(可调整)
revenue_weight = 0.8
inventory_weight = 0.2

def obj_rule(model):
    total_revenue = 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
    )
    total_remaining_inventory = sum(
        model.current_inventory[sku] - 
        calculate_demand(model.current_price[sku], model.regular_price[sku],
                         model.price_elasticity[sku], model.current_demand[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_duration[sku]
        for sku in model.SKUs
    )
    # 最大化收入,最小化剩余库存等价于最大化(收入 - 加权剩余库存)
    return revenue_weight * total_revenue - inventory_weight * total_remaining_inventory

model.obj = Objective(rule=obj_rule, sense=maximize)

6. 促销触发逻辑约束错误

原问题规则是:若按常规价格销售N周后库存≥初始50%,则可启动促销,当前promo_switch_con的逻辑是强制要求销售4周后库存≥50%,这不符合"可选择促销"的规则,应改为:如果启动促销(即promo_duration > 0),则必须满足销售N周后库存≥初始50%。需引入二进制变量表示是否启动促销:

# 添加二进制变量:1表示启动促销,0表示不启动
model.promo_active = Var(model.SKUs, within=Binary, initialize=0)

# 促销触发约束:如果启动促销,那么销售N周(比如N=4)后的库存≥初始50%
def promo_trigger_rule(model, sku):
    N = 4
    regular_demand = calculate_demand(model.current_price[sku], model.regular_price[sku],
                                      model.price_elasticity[sku], model.current_demand[sku])
    inventory_after_N = model.current_inventory[sku] - regular_demand * N
    # 大M法:当promo_active=1时,inventory_after_N >= 0.5*base_inventory;promo_active=0时无约束
    return inventory_after_N >= 0.5 * model.current_inventory[sku] - (1 - model.promo_active[sku]) * 1e6
model.promo_trigger_con = Constraint(model.SKUs, rule=promo_trigger_rule)

# 促销时长与促销激活变量关联:如果promo_active=0,则promo_duration=0
def promo_duration_rule(model, sku):
    return model.promo_duration[sku] <= model.promo_active[sku] * 12  # 12为最大可能时长
model.promo_duration_con = Constraint(model.SKUs, rule=promo_duration_rule)

7. 求解器选择问题

模型包含整数变量(regular_duration、promo_duration、promo_active),IPOPT是连续优化求解器,无法处理整数变量,需切换到支持混合整数非线性规划的求解器,比如bonmin或couenne,或调整变量为连续值(若允许时长为小数)。

内容的提问来源于stack exchange,提问作者user3490622

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 09:17:36