想象一下你是一家小型电子厂的厂长,每天要处理几十个订单,每台机器都有不同的加工能力,工人也有不同的技能水平。如何安排生产顺序才能既按时交货,又降低能耗成本?这就是典型的生产调度问题。
我在给一家玩具厂做咨询时遇到过真实案例:他们原先靠老师傅经验排产,旺季时经常出现机器闲置和加班并存的情况。后来我们用Python+Cplex搭建了优化模型,三个月内将设备利用率提高了22%,订单准时交付率从68%提升到91%。
这类问题本质上属于组合优化范畴,具有以下特点:
传统Excel手工排产就像用算盘解微积分,而Cplex这类数学规划求解器相当于给你一台超级计算机。它采用分支定界、割平面等算法,能高效处理数百万变量的复杂问题。
推荐使用Python 3.8+和Cplex 22.1+组合,实测中发现新版对MIP(混合整数规划)求解效率提升明显。安装时直接运行:
bash复制pip install cplex docplex
这里同时安装了docplex,它是IBM官方提供的更友好的Python API封装,后续我们会对比两种写法的差异。
常见安装问题排查:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple cplex让我们用经典的生产计划问题练手:某工厂生产A、B两种产品,需要经过加工和装配两道工序,具体参数如下:
| 产品 | 加工耗时(h) | 装配耗时(h) | 利润(元) |
|---|---|---|---|
| A | 2 | 1 | 300 |
| B | 1 | 2 | 400 |
每周总工时限制:加工工序≤80h,装配工序≤60h。如何安排生产使利润最大?
python复制import cplex
model = cplex.Cplex()
model.objective.set_sense(model.objective.sense.maximize)
# 定义变量
products = ['A', 'B']
model.variables.add(
names=products,
lb=[0, 0], # 生产量不能为负
types=['I', 'I'], # 整数变量
obj=[300, 400] # 目标函数系数
)
# 添加约束
constraints = [
[['A', 'B'], [2, 1]], # 加工工序
[['A', 'B'], [1, 2]] # 装配工序
]
model.linear_constraints.add(
lin_expr=constraints,
senses=['L', 'L'], # 小于等于
rhs=[80, 60], # 工时上限
names=['process', 'assembly']
)
model.solve()
print("最优生产方案:")
for i, p in enumerate(products):
print(f"{p}: {model.solution.get_values(i)}件")
print(f"预计周利润:{model.solution.get_objective_value():.2f}元")
实际生产中常遇到"工序B必须在工序A完成后才能开始"的约束。假设我们要给产品X安排生产时段:
python复制# 定义工序时间变量
model.variables.add(
names=['start_A', 'end_A', 'start_B', 'end_B'],
lb=[0, 0, 0, 0],
types=['C']*4
)
# 工序持续时间约束
model.linear_constraints.add(
lin_expr=[
[['start_A', 'end_A'], [1, -1]], # end_A - start_A ≥ 2
[['start_B', 'end_B'], [1, -1]] # end_B - start_B ≥ 3
],
senses=['G', 'G'],
rhs=[2, 3],
names=['dur_A', 'dur_B']
)
# 工序先后约束
model.linear_constraints.add(
lin_expr=[[['end_A', 'start_B'], [1, -1]]], # start_B ≥ end_A
senses=['G'],
rhs=[0],
names=['seq_AB']
)
当多任务共享同一设备时,需要确保时间不重叠。这里引入指示变量技巧:
python复制# 为每对任务添加二元变量
y = model.variables.add(
names=['y_12', 'y_21'],
types=['B', 'B'] # 二进制变量
)
# 大M法实现互斥
M = 1000 # 足够大的常数
model.linear_constraints.add(
lin_expr=[
[['end_1', 'start_2', 'y_12'], [1, -1, M]], # end1 ≤ start2 + M*y12
[['end_2', 'start_1', 'y_21'], [1, -1, M]] # end2 ≤ start1 + M*y21
],
senses=['L', 'L'],
rhs=[M, M]
)
# 确保两个y不同时为0
model.linear_constraints.add(
lin_expr=[[['y_12', 'y_21'], [1, 1]]],
senses=['G'],
rhs=[1]
)
生产调度往往需要平衡多个目标,比如:
Cplex支持分层求解法和加权法两种处理方式。这里展示加权法实现:
python复制# 设置复合目标函数
model.objective.set_linear([
('profit', 1.0), # 利润权重
('delay', -0.5), # 延迟惩罚
('idle_time', -0.2) # 空闲惩罚
])
# 启用并行求解
model.parameters.threads.set(4) # 使用4个CPU核心
model.parameters.mip.strategy.search.set(
model.parameters.mip.strategy.search.parallel
)
对于大规模问题,可以实时监控求解进度:
python复制# 设置回调函数
def callback(info):
if info.has_incumbent():
gap = 100 * info.get_mip_relative_gap()
print(f"当前解:{info.get_objective_value()} 间隙:{gap:.2f}%")
model.set_progress_callback(callback)
# 限制求解时间
model.parameters.timelimit.set(600) # 10分钟
实际项目中,原始数据往往需要清洗:
clip方法限制合理范围python复制import pandas as pd
# 读取生产数据
df = pd.read_excel('production_data.xlsx')
# 处理缺失值
df['setup_time'] = df['setup_time'].fillna(
df.groupby('machine_type')['setup_time'].transform('median')
)
# 剔除异常值
df = df[df['process_time'] < df['process_time'].quantile(0.99)]
对于持续运行的排产系统,推荐架构:
code复制[ERP系统] → [Redis缓存] → [Flask API] → [Cplex求解集群]
↖________[结果可视化]________↙
关键配置参数:
python复制# 分布式求解设置
model.parameters.parallel.set(1) # 分布式模式
model.parameters.mip.limits.treememory.set(8192) # 内存限制(MB)
model.parameters.workdir.set('/opt/cplex_temp') # 临时文件路径
当model.solve()返回不可行状态时,可以:
python复制model.conflict.refine(model.conflict.all_constraints())
print("冲突约束:")
for c in model.conflict.get():
print(model.linear_constraints.get_names(c))
python复制# 临时放宽工时限制10%
original_rhs = model.linear_constraints.get_rhs()
model.linear_constraints.set_rhs([x * 1.1 for x in original_rhs])
遇到求解速度慢时,可以尝试:
python复制model.start.set_start([('A', 10), ('B', 20)])
python复制model.parameters.emphasis.mip.set(3) # 隐藏可行解优先
python复制model.parameters.mip.pool.capacity.set(10) # 保存10个次优解
model.populate_solution_pool()
for i in range(model.solution.pool.get_num()):
print(f"方案{i}: {model.solution.pool.get_values(i)}")