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

OR-tools约束优化:如何实现快递包裹阶梯式计价规则?

OR-Tools阶梯计价约束优化解决方案

原代码核心问题

  1. 决策变量条件判断错误:目标函数中直接用all_courier_sums[courier_num] >= 2这类Python条件判断是无效的——决策变量在模型求解前是未知的,无法通过Python分支逻辑处理。
  2. 变量关联缺失:all_courier_sums(快递商承接包裹数)未与parcel_by_courier(包裹分配变量)建立约束关联,导致模型无法正确统计每个快递商的承接数量。
  3. 多件约束写法错误:原代码中用Python条件判断生成约束的方式逻辑混乱,应直接针对courier_further_item_allowed为False的快递商添加承接数量≤1的约束。

二阶计价实现(首件+后续件)

以下是修正后的完整代码,实现「首件一个价格,后续所有件另一个价格」的计价规则:

def cost_optimisation():
    # 初始化求解器
    solver = pywraplp.Solver.CreateSolver('SCIP')

    # 获取数据
    data = get_model_data()
    max_parcels = len(data['parcel_range'])

    ### 变量定义 ###
    # 包裹-快递商分配变量:1表示快递商承接该包裹,0表示不承接
    parcel_by_courier = {}
    for parcel_num in data['parcel_range']:
        for courier_num in data['courier_range']:
            parcel_by_courier[parcel_num, courier_num] = solver.BoolVar(f"parcel_{parcel_num}_courier_{courier_num}")

    # 快递商承接总包裹数
    all_courier_sums = {}
    for courier_num in data['courier_range']:
        all_courier_sums[courier_num] = solver.IntVar(0, max_parcels, f"{data['courier_names'][courier_num]}_total_parcels_shipped")

    # 辅助布尔变量:标记快递商是否承接至少1件包裹(用于首件计价触发)
    courier_has_parcel = {}
    for courier_num in data['courier_range']:
        courier_has_parcel[courier_num] = solver.BoolVar(f"courier_{courier_num}_has_parcel")

    print('变量总数 =', solver.NumVariables(), '\n')

    ### 约束定义 ###
    # 1. 所有包裹必须被分配给唯一快递商
    for parcel_num in data['parcel_range']:
        solver.Add(sum(parcel_by_courier[parcel_num, courier_num] for courier_num in data['courier_range']) == 1)

    # 2. 关联快递商承接数量与包裹分配变量
    for courier_num in data['courier_range']:
        solver.Add(all_courier_sums[courier_num] == sum(parcel_by_courier[parcel_num, courier_num] for parcel_num in data['parcel_range']))

    # 3. 重量与尺寸约束:仅当快递商承接包裹时,约束生效
    for parcel_num in data['parcel_range']:
        for courier_num in data['courier_range']:
            solver.Add(parcel_by_courier[parcel_num, courier_num] * data['parcel_weights'][parcel_num] <= data['courier_weights'][courier_num])
            solver.Add(parcel_by_courier[parcel_num, courier_num] * data['parcel_largest_dimensions'][parcel_num] <= data['courier_largest_dimensions'][courier_num])

    # 4. 限制不可承接多件的快递商最多承接1件
    for courier_num in data['courier_range']:
        if not data['courier_further_item_allowed'][courier_num]:
            solver.Add(all_courier_sums[courier_num] <= 1)

    # 5. 辅助变量约束:courier_has_parcel=1 当且仅当 快递商承接至少1件
    for courier_num in data['courier_range']:
        solver.Add(all_courier_sums[courier_num] >= courier_has_parcel[courier_num])
        solver.Add(all_courier_sums[courier_num] <= max_parcels * courier_has_parcel[courier_num])

    print('约束总数 =', solver.NumConstraints(), '\n')

    ### 目标函数 ###
    total_cost = solver.Sum(
        # 首件成本:仅当快递商承接包裹时收取
        data['courier_first_item_pence'][courier_num] * courier_has_parcel[courier_num] +
        # 后续件成本:总件数减1(首件)乘以后续件单价
        data['courier_further_item_pence'][courier_num] * (all_courier_sums[courier_num] - courier_has_parcel[courier_num])
        for courier_num in data['courier_range']
    )

    solver.Minimize(total_cost)
    status = solver.Solve()

    # 输出结果
    if status == solver.OPTIMAL:
        print('最优成本 =', solver.Objective().Value(), '便士')
        for courier_num in data['courier_range']:
            cnt = int(all_courier_sums[courier_num].solution_value())
            if cnt > 0:
                print(f"快递商 {data['courier_names'][courier_num]} 承接 {cnt} 件包裹")
                for parcel_num in data['parcel_range']:
                    if parcel_by_courier[parcel_num, courier_num].solution_value() == 1:
                        print(f"  - 包裹 {parcel_num}")
    else:
        print('未找到最优解')

三阶计价扩展(首件+第2-5件+第6件及以上)

如果需要实现三阶阶梯计价,只需新增辅助变量标记阶梯区间,并调整目标函数计算逻辑:

def cost_optimisation():
    # 初始化求解器
    solver = pywraplp.Solver.CreateSolver('SCIP')

    # 获取数据
    data = get_model_data()
    max_parcels = len(data['parcel_range'])

    ### 变量定义 ###
    parcel_by_courier = {}
    for parcel_num in data['parcel_range']:
        for courier_num in data['courier_range']:
            parcel_by_courier[parcel_num, courier_num] = solver.BoolVar(f"parcel_{parcel_num}_courier_{courier_num}")

    all_courier_sums = {}
    for courier_num in data['courier_range']:
        all_courier_sums[courier_num] = solver.IntVar(0, max_parcels, f"{data['courier_names'][courier_num]}_total_parcels_shipped")

    # 辅助布尔变量:标记不同阶梯区间
    courier_has_parcel = {}  # 至少1件(首件阶梯)
    courier_has_mid = {}     # 至少2件(第2-5件阶梯)
    courier_has_high = {}    # 至少6件(第6件及以上阶梯)
    for courier_num in data['courier_range']:
        courier_has_parcel[courier_num] = solver.BoolVar(f"courier_{courier_num}_has_parcel")
        courier_has_mid[courier_num] = solver.BoolVar(f"courier_{courier_num}_has_mid")
        courier_has_high[courier_num] = solver.BoolVar(f"courier_{courier_num}_has_high")

    print('变量总数 =', solver.NumVariables(), '\n')

    ### 约束定义 ###
    # 1. 所有包裹必须分配
    for parcel_num in data['parcel_range']:
        solver.Add(sum(parcel_by_courier[parcel_num, courier_num] for courier_num in data['courier_range']) == 1)

    # 2. 关联承接数量与包裹分配变量
    for courier_num in data['courier_range']:
        solver.Add(all_courier_sums[courier_num] == sum(parcel_by_courier[parcel_num, courier_num] for parcel_num in data['parcel_range']))

    # 3. 重量与尺寸约束
    for parcel_num in data['parcel_range']:
        for courier_num in data['courier_range']:
            solver.Add(parcel_by_courier[parcel_num, courier_num] * data['parcel_weights'][parcel_num] <= data['courier_weights'][courier_num])
            solver.Add(parcel_by_courier[parcel_num, courier_num] * data['parcel_largest_dimensions'][parcel_num] <= data['courier_largest_dimensions'][courier_num])

    # 4. 单包裹限制
    for courier_num in data['courier_range']:
        if not data['courier_further_item_allowed'][courier_num]:
            solver.Add(all_courier_sums[courier_num] <= 1)

    # 5. 辅助变量约束
    for courier_num in data['courier_range']:
        # 至少1件
        solver.Add(all_courier_sums[courier_num] >= courier_has_parcel[courier_num])
        solver.Add(all_courier_sums[courier_num] <= max_parcels * courier_has_parcel[courier_num])

        # 至少2件(必须先满足至少1件)
        solver.Add(all_courier_sums[courier_num] >= 2 * courier_has_mid[courier_num])
        solver.Add(all_courier_sums[courier_num] <= max_parcels * courier_has_mid[courier_num] + 1 * (1 - courier_has_mid[courier_num]))
        solver.Add(courier_has_mid[courier_num] <= courier_has_parcel[courier_num])

        # 至少6件(必须先满足至少2件)
        solver.Add(all_courier_sums[courier_num] >= 6 * courier_has_high[courier_num])
        solver.Add(all_courier_sums[courier_num] <= max_parcels * courier_has_high[courier_num] + 5 * (1 - courier_has_high[courier_num]))
        solver.Add(courier_has_high[courier_num] <= courier_has_mid[courier_num])

    print('约束总数 =', solver.NumConstraints(), '\n')

    ### 目标函数 ###
    total_cost = solver.Sum(
        # 首件成本
        data['courier_first_item_pence'][courier_num] * courier_has_parcel[courier_num] +
        # 第2-5件成本:最多4件,计算总件数减首件后,减去超过5件的部分
        data['courier_mid_item_pence'][courier_num] * (
            (all_courier_sums[courier_num] - courier_has_parcel[courier_num]) - 
            solver.Max(0, all_courier_sums[courier_num] - 5)
        ) +
        # 第6件及以上成本:总件数减5后的正数部分
        data['courier_high_volume_item_pence'][courier_num] * solver.Max(0, all_courier_sums[courier_num] - 5)
        for courier_num in data['courier_range']
    )

    solver.Minimize(total_cost)
    status = solver.Solve()

    # 输出结果
    if status == solver.OPTIMAL:
        print('最优成本 =', solver.Objective().Value(), '便士')
        for courier_num in data['courier_range']:
            cnt = int(all_courier_sums[courier_num].solution_value())
            if cnt > 0:
                print(f"快递商 {data['courier_names'][courier_num]} 承接 {cnt} 件包裹")
                for parcel_num in data['parcel_range']:
                    if parcel_by_courier[parcel_num, courier_num].solution_value() == 1:
                        print(f"  - 包裹 {parcel_num}")
    else:
        print('未找到最优解')

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 03:24:25