【问题标题】:Delivery price depending on order value with multiple thresholds交货价格取决于具有多个阈值的订单价值
【发布时间】:2022-01-01 21:43:23
【问题描述】:

假设我们从不同的供应商处购买 N 种产品。我们有许多供应商,但并非每个供应商都能提供所有产品。每个零售商都知道每种产品的价格,除非该产品在那里不可用。每个卖家还有运费。对于来自给定供应商的所有订单,只需支付一次运费。它也可能受到特殊限制,例如如果总订单价值超过 179 美元,则免运费,否则为 15.99 美元,或者如果不符合最低订单价值,则无法发货,例如100 美元。每个卖家的运费可能会有所不同。根据选择的产品以及来自不同供应商的定价和运费信息,假设我们总是想购买所有产品,我们如何优化整个产品购物车的总成本(产品价格 + 运费)?

目前,我可以解决每个供应商的固定交付成本问题,但我无法处理考虑到给定供应商订单价值的条件约束,例如:

  • 订单> = 179 PLN = 免费送货
  • 订单 = 100 PLN = 交货 15.99 PLN
  • 最低订购量 = 100 兹罗提(低于此数量,指定供应商无法订购。

关于配送成本数据的结构,“关键”是指某个供应商满足“价值”(即配送成本)的最低金额。如果给定供应商的“product_prices”下的产品成本为 99999999,则表示该产品不可用。以下是示例数据:

 'delivery_prices': {
    'supplier1': {
      100: 14.9,
    },
    'supplier2': {
      0: 19.99,
      179: 0,
    },
    'supplier3': {
      100: 15,
      200: 10,
      250: 0,
    },
  }

这是我目前的优化代码:

import pyomo.environ as pe
import pyomo.opt as po

solver = po.SolverFactory('glpk')

def optimization_problem(input_data, solver):
    model = pe.ConcreteModel("All products filled")

    # sets
    model.I = pe.Set(initialize=input_data['supplier_names'])
    model.J = pe.Set(initialize=input_data['product_names'])

    # parameters
    model.s = pe.Param(model.I, initialize=input_data['supplier_stock'])
    model.d = pe.Param(model.J, initialize=input_data['demand'])
    model.f = pe.Param(model.I, initialize=input_data['delivery_prices'])
    model.c = pe.Param(model.I, model.J, initialize=input_data['product_prices'])

    # variables
    model.x = pe.Var(model.I, model.J, domain=pe.NonNegativeReals)
    model.y = pe.Var(model.I, domain=pe.Binary) # product quantity for given suppliersupplier

    # constraints
    # if sum 
    def con_satisfaction(model, j):
        return sum(model.x[i, j] for i in model.I) >= model.d[j]
    model.con_satisfaction = pe.Constraint(model.J, rule=con_satisfaction)

    def con_transportation(model, i):
        return sum(model.x[i, j] for j in model.J) <= model.s[i] * model.y[i]
    model.con_transportation = pe.Constraint(model.I, rule=con_transportation)

    # objective
    def obj_min_cost(model):
        return sum(model.f[i] * model.y[i] for i in model.I)\
            + sum(model.c[i, j] * model.x[i, j] for i in model.I for j in model.J)
    model.obj_min_cost = pe.Objective(sense=pe.minimize, rule=obj_min_cost)
    
    solver.solve(model)

    products_distribution = { key: value for key, value in model.x.extract_values().items() if value > 0}
    products_suppliers = list(set([product[0] for product in products_distribution.keys()]))
    suppliers_delivery_prices = {supplier:price for supplier, price in input_data['delivery_prices'].items() if supplier in products_suppliers}
    
    return (model.obj_min_cost(), products_suppliers, suppliers_delivery_prices, products_distribution)


# EXECUTE FUNCTION
optimization_problem(example_dataset, solver)

是否有人对如何纳入有条件交货价格有任何指导?

【问题讨论】:

  • 您将不得不为每个供应商设置一组稍微复杂的二进制变量来表示您在该供应商的哪个“层”,并链接该二进制变量,由“层”索引" 到从该供应商处购买的金额。
  • @AirSquid 感谢您的回复,但是如何设置最小订单的约束,即对于供应商 1 和供应商 3 - 如果总订单低于 100,则无法实现。此外,您能否使用您建议的解决方案的代码为我提供更多指导?

标签: python optimization pyomo


【解决方案1】:

这是一个引入二进制变量的工作示例,按供应商和层级索引。还添加了 2 个约束,用于将层链接到供应商数量的上下限(链接约束)。

import pyomo.environ as pe

solver = pe.SolverFactory('glpk')

s1, s2 = 'supplier 1', 'supplier 2'
p1, p2 = 'bike', 'cellphone'

# some fake data
input_data = {  'supplier_names'    : [s1, s2],
                'product_names'     : [p1, p2],
                'supplier_stock'    : {(s1, p1): 1000, (s1, p2): 500, (s2, p1): 700, (s2, p2): 400},
                'demand'            : {p1: 50, p2: 80},
                'product_prices'    : {(s1, p1): 100, (s1, p2): 150, (s2, p1): 90, (s2, p2): 160}
                }
max_products = 10000 # some reasonable large "big M" upper bound on the total number of products from a supplier
penalty = 10000  # some reasonably large penalty...

                    # supplier    tier                 (qty, price)
delivery_prices = {   s1:        {'below min'   :      (0, penalty),    # <100 N/A, use penalty
                                  '1A'          :      (100, 19.99),
                                  '1B'          :      (200, 0.00)},    
                      s2:        {'regular'     :      (0, 25.00),
                                  'free'        :      (150, 0.00)} }

# make a convenience set of all the tier label possiblities accross all suppliers...
all_tiers = set()
for k in delivery_prices:
    all_tiers.update(set(delivery_prices[k].keys()))

model = pe.ConcreteModel("All products filled")

# sets
model.suppliers = pe.Set(initialize=input_data['supplier_names'])
model.products = pe.Set(initialize=input_data['product_names'])
model.tiers = pe.Set(initialize=list(all_tiers))
model.supplier_tiers = pe.Set(within=model.suppliers * model.tiers, initialize={(s,t) for s in delivery_prices
                                for t in delivery_prices[s]})

# parameters
model.s = pe.Param(model.suppliers, model.products, initialize=input_data['supplier_stock'])
model.d = pe.Param(model.products, initialize=input_data['demand'])
model.c = pe.Param(model.suppliers, model.products, initialize=input_data['product_prices'])

# variables
model.x = pe.Var(model.suppliers, model.products, domain=pe.NonNegativeReals) # product quantity for given suppliersupplier
model.y = pe.Var(model.supplier_tiers, domain=pe.Binary)       # buy from supplier s at tier t 


# constraints

# link quantity to supplier tier LB
def tier(model, supplier, tier):
    return sum(model.x[supplier, p] for p in model.products) >= model.y[supplier, tier]*delivery_prices[supplier][tier][0]
model.tier_min = pe.Constraint(model.supplier_tiers, rule=tier)

# link quantity to supplier UB...  this forces selection of some tier to get anything from supplier
def upper(model, supplier):
    return sum(model.x[supplier, p] for p in model.products) <= sum(model.y[supplier, tier] for tier in model.tiers
        if (supplier, tier) in model.supplier_tiers) * max_products
model.upper_bound = pe.Constraint(model.suppliers, rule=upper)

# meet demand
def demand(model, product):
    return sum(model.x[supplier, product] for supplier in model.suppliers) >= model.d[product]
model.demand = pe.Constraint(model.products, rule=demand)


# objective
def obj_min_cost(model):
    return sum(model.x[s, p] * model.c[s, p] for s in model.suppliers for p in model.products) + \
    sum(model.y[s, t] * delivery_prices[s][t][1] for s,t in model.supplier_tiers)
model.obj_min_cost = pe.Objective(sense=pe.minimize, rule=obj_min_cost)

result = solver.solve(model)
print(result)
model.display()

# products_distribution = { key: value for key, value in model.x.extract_values().items() if value > 0}
# products_suppliers = list(set([product[0] for product in products_distribution.keys()]))
# suppliers_delivery_prices = {supplier:price for supplier, price in input_data['delivery_prices'].items() if supplier in products_suppliers}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-18
    相关资源
    最近更新 更多