排程算法输出的方案在生产环境中被判不可行,往往不是算法本身的问题,而是约束建模出现了矛盾,或者系统中存在未被识别的瓶颈资源。一台设备的日历配置错了、一道工序的前置关系漏写了、一个物料的供给窗口没对上,都可能导致求解器直接返回不可行解,或者给出一个执行层面根本落不了地的计划。要彻底解决这类问题,需要从约束条件的梳理和瓶颈资源的定位两条线同时入手,本文将结合具体案例和代码展开分析。

一、先分清约束类型:不可行的根源多半在硬约束冲突
排程模型里的约束通常分为硬约束和软约束两类。硬约束是绝对不能违反的规则,比如设备同一时刻只能加工一个任务、任务必须在其前置任务完成之后才能开始、交付日期的截止线不能突破。软约束则是尽量满足的目标,比如缩短换型时间、均衡各设备的负载,违反了只是产生惩罚成本,不会导致方案不可行。
很多团队在建模时习惯把业务规则一股脑全部设置成硬约束,这是排程不可行最常见的诱因。举个例子,某工厂要求订单必须在承诺交期前完成,同时又要求所有任务必须在白班时段执行,而白班的总产能根本不够覆盖订单量,这两个约束单独看都合理,放在一起就构成了数学上的无解。正确的做法是把交期这类带有商务属性的约束降级为软约束,用延迟惩罚函数来驱动求解器尽量靠近目标,而不是一刀切地卡死。
排查约束冲突时,建议维护一份约束清单,标注每条约束的类型、来源和可放松空间。可以用下面的结构来组织:
constraints = [
{"id": "C01", "type": "hard", "rule": "machine_capacity",
"desc": "同一设备同一时刻只能处理一个任务", "relaxable": False},
{"id": "C02", "type": "hard", "rule": "precedence",
"desc": "装配必须在零件加工完成后开始", "relaxable": False},
{"id": "C03", "type": "hard", "rule": "calendar",
"desc": "任务只能在设备可用日历内执行", "relaxable": False},
{"id": "C04", "type": "soft", "rule": "due_date",
"desc": "订单不晚于承诺交期完成", "relaxable": True,
"penalty": 1000},
]
当求解器报告不可行时,优先检查硬约束之间是否存在结构性矛盾。一个实用技巧是做约束放松实验:每次只放松一条硬约束重新求解,如果放松某条约束后立刻得到可行解,那这条约束就是冲突的关键点,再顺着它反查是业务规则不合理还是数据配置有误。
二、三类高频出错点:容量、时间窗口与工序依赖
第一类是资源容量约束。设备的班次日历、维护计划、人员技能矩阵,任何一个数据源不准确,都会让模型里的可用容量与真实容量脱节。特别是节假日和临时检修,如果维护计划没有同步到排程系统,模型会认为设备可用而实际不可用,产出的方案自然执行不下去。建议定期对账,把ERP或MES里的实际工时与排程系统的理论容量做比对,偏差超过百分之五就要触发数据校准流程。
第二类是时间窗口约束。典型场景包括物料到达时间、模具准备时间、客户指定的时间段。这类约束最容易出问题的地方在于时区处理和时间粒度不统一。比如物料系统按天记录到达日期,排程引擎却按小时切分时间片,直接把到达时间默认为零点,结果排出的方案在物料实际未到港的上午就开始投料。统一时间基准并显式建模等待时间,是避免这类问题的有效手段。
第三类是工序依赖约束。除了常见的串行依赖,实际生产中还大量存在重叠依赖,也就是后道工序可以在前道工序完成一部分后就开始。如果把重叠依赖错误地建成完全串行,虽然不会导致不可行,但会严重压缩计划的可行空间,让本可以按期完成的订单被判为逾期,进而触发交期硬约束冲突。用网络图把依赖关系可视化,人工抽检关键路径上的依赖类型,能显著降低这类建模错误。
三、瓶颈分析:用数据说话定位卡脖子资源
约束条件全部检查无误之后,如果方案仍然频繁不可行,瓶颈资源往往是罪魁祸首。瓶颈是指整个系统中产能最紧张、制约整体产出的那一环,它的利用率接近百分之百,任何一点扰动都会造成连锁延误。定位瓶颈不能靠经验猜测,要靠数据统计。
第一步做利用率分析。统计每个资源在计划周期内的负荷率,即分配的工时除以可用工时。可以用如下代码快速找出高负荷资源:
def find_bottlenecks(resources, tasks, horizon_days=30):
# resources: 资源列表,tasks: 已分配任务列表
load = {r.id: 0.0 for r in resources}
capacity = {}
for r in resources:
# 可用工时 = 日历工时 - 维护占用
capacity[r.id] = r.calendar_hours * horizon_days - r.maintenance_hours
for t in tasks:
if t.assigned_resource in load:
load[t.assigned_resource] += t.planned_hours
bottleneck = []
for rid in load:
ratio = load[rid] / capacity[rid] if capacity[rid] > 0 else 0
if ratio >= 0.95:
bottleneck.append((rid, round(ratio, 3)))
return sorted(bottleneck, key=lambda x: -x[1])
# 返回示例: [('M07', 1.12), ('M03', 0.98)]
# M07 负荷率超过 100%,说明该设备已被过度分配,是明显的瓶颈
第二步做关键链路追踪。从交付日期倒推,找出决定项目总工期的那条任务链,链上耗时最长的资源就是约束全局产出的瓶颈。第三步做松弛度分析,计算每个资源前的缓冲时间,松弛度接近零甚至为负的资源,就是需要优先扩产或者外协的对象。这三步做完,瓶颈在哪里、影响多大、怎么缓解,基本一目了然。
针对已确认的瓶颈,常见的缓解手段包括:在瓶颈设备前设置时间缓冲,避免上游波动直接冲击瓶颈;把瓶颈资源上的任务优先级重新排序,让高价值订单优先占用;对非瓶颈工序错峰排产,把等待中的在制品数量控制住;实在不行就安排外协或者评估增购设备。要注意的是,消除一个瓶颈后,系统的瓶颈会转移到别处,所以瓶颈分析应该作为周期性动作持续进行,而不是一次性的救火。
四、建立约束冲突诊断机制,让问题自动浮出水面
与其每次不可行都靠人工排查,不如在系统层面建立自动诊断机制。核心思路是给求解器加上不可行原因分析模块,当求解失败时,自动输出冲突的约束组合和涉及的资源任务清单。多数现代求解器都支持IIS(不可约不可行子系统)分析,即找出一组最少的互相冲突的约束,这组约束去掉任何一条就能恢复可行,直接指向问题核心。
from ortools.sat.python import cp_model
def solve_with_diagnosis(model, solver):
status = solver.Solve(model)
if status == cp_model.INFEASIBLE:
# 尝试定位不可行原因
# 对每条约束逐个假设性删除,观察是否恢复可行
conflicts = []
for ct in model.Proto().constraints:
trial = model.Clone()
# 移除当前约束后重新求解
# 若可行则记录该约束为冲突参与者
pass
return {"feasible": False, "conflicts": conflicts}
return {"feasible": True,
"makespan": solver.ObjectiveValue()}
把诊断结果以业务人员能看懂的语言输出,比如告知计划员是三号车间的热处理设备日历与订单的交期窗口冲突,而不是抛出一堆求解器内部变量名,这样一线人员才能快速响应。同时建议保留每次排程的约束快照和诊断日志,长期积累下来就能识别出哪些约束频繁参与冲突,从制度层面推动数据治理和规则优化,形成排程质量的正向循环。
总结一下,解决排程方案不可行需要先理清硬约束与软约束的边界,逐一核对容量、时间窗口和依赖关系这三类高频出错点,再通过利用率、关键链路和松弛度三个维度定位瓶颈资源,最后用自动化诊断机制把被动的救火变成主动的预防。约束建模没有一劳永逸的完美方案,持续的数据校准和周期性的瓶颈审视,才是让排程系统长期稳定产出可行计划的根本保障。