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

如何在OR-Tools CP-Sat中实现跨停机时段的任务续加工?

用OR-Tools CP-Sat实现支持机器停机中断续加工的柔性作业车间调度

问题概述

需要实现基于OR-Tools CP-Sat的柔性作业车间调度,核心需求是:任务在机器上加工时可被机器停机中断,停机结束后继续完成剩余加工;最终目标是最小化完工时间。现有方案要么无法避免任务在停机时段加工,要么不支持任务跨停机时段续加工,因此需要重新建模。

解决方案思路

  • 预处理机器停机区间,生成连续的可用时段
  • 对每个任务操作,选定机器后将其加工拆分到机器的多个可用时段中,总加工时长严格等于任务要求的时长
  • 约束每个可用时段内的任务加工区间不重叠,保证机器独占性
  • 结合工件操作的先后工艺约束,最小化整体完工时间

完整代码实现

from ortools.sat.python import cp_model
from typing import List, Dict, Tuple

def main():
    # ---------------------- 问题参数定义 ----------------------
    # 柔性作业车间:每个工件的操作序列,每个操作可选机器及对应加工时长
    # 格式:jobs[工件id][操作id] = [(机器id, 加工时长), ...]
    jobs = [
        # 工件0:2个操作
        [(0, 3), (1, 4)],
        # 工件1:3个操作
        [(1, 2), (2, 3), (0, 2)],
        # 工件2:2个操作
        [(2, 5), (0, 3)]
    ]
    # 机器停机区间:key=机器id,value=停机时段列表(start, end)
    machine_downtime_intervals = {
        0: [(2, 5)],
        1: [(2, 3), (6, 7), (9, 14)],
        2: [(3, 5)]
    }
    num_machines = max(machine_downtime_intervals.keys()) + 1
    num_jobs = len(jobs)

    # ---------------------- 预处理机器可用区间 ----------------------
    # 预估最大完工时间(可根据任务总时长+停机总时长估算)
    total_processing_time = sum(
        min(dur for (_, dur) in op)
        for job in jobs
        for op in job
    )
    total_downtime = sum(
        end - start
        for dt_list in machine_downtime_intervals.values()
        for (start, end) in dt_list
    )
    horizon = total_processing_time + total_downtime + 10  # 加冗余量

    machine_available_intervals: Dict[int, List[Tuple[int, int]]] = {}
    for machine in range(num_machines):
        dt_intervals = sorted(machine_downtime_intervals.get(machine, []))
        available = []
        current_start = 0
        for (dt_start, dt_end) in dt_intervals:
            if current_start < dt_start:
                available.append((current_start, dt_start))
            current_start = dt_end
        # 添加最后一个可用区间到预估的horizon
        available.append((current_start, horizon))
        machine_available_intervals[machine] = available

    # ---------------------- 创建CP-Sat模型 ----------------------
    model = cp_model.CpModel()

    # 变量定义
    # 1. 操作选中机器的布尔变量:selected[job_id][op_id][machine_id]
    selected = {}
    # 2. 操作在机器可用区间内的加工变量:
    #    op_start[job_id][op_id][machine_id][interval_idx]
    #    op_end[job_id][op_id][machine_id][interval_idx]
    #    op_duration[job_id][op_id][machine_id][interval_idx]
    op_start = {}
    op_end = {}
    op_duration = {}
    # 3. 每个操作的实际开始/结束时间(跨可用区间的整体时间)
    job_op_start = {}
    job_op_end = {}
    # 4. 完工时间
    makespan = model.NewIntVar(0, horizon, "makespan")

    # 存储各机器的加工区间,用于无重叠约束
    machine_intervals = {}

    for job_id in range(num_jobs):
        for op_id in range(len(jobs[job_id])):
            # 操作的可选机器
            allowed_machines = [m for (m, _) in jobs[job_id][op_id]]
            # 操作的要求加工时长(按机器区分)
            required_duration = {m: dur for (m, dur) in jobs[job_id][op_id]}

            # 创建选中机器的布尔变量
            selected[(job_id, op_id)] = {}
            for machine in allowed_machines:
                selected[(job_id, op_id)][machine] = model.NewBoolVar(f"selected_job{job_id}_op{op_id}_machine{machine}")

            # 约束:必须选中一台机器
            model.Add(sum(selected[(job_id, op_id)][m] for m in allowed_machines) == 1)

            # 初始化操作的整体开始/结束时间变量
            job_op_start[(job_id, op_id)] = model.NewIntVar(0, horizon, f"job{job_id}_op{op_id}_start")
            job_op_end[(job_id, op_id)] = model.NewIntVar(0, horizon, f"job{job_id}_op{op_id}_end")

            for machine in allowed_machines:
                avail_intervals = machine_available_intervals[machine]
                op_start[(job_id, op_id, machine)] = []
                op_end[(job_id, op_id, machine)] = []
                op_duration[(job_id, op_id, machine)] = []

                total_dur = model.NewIntVar(0, required_duration[machine], f"total_dur_job{job_id}_op{op_id}_machine{machine}")
                # 选中机器时,总加工时长等于要求值;未选中时为0
                model.Add(total_dur == required_duration[machine]).OnlyEnforceIf(selected[(job_id, op_id)][machine])
                model.Add(total_dur == 0).OnlyEnforceIf(selected[(job_id, op_id)][machine].Not())

                # 为每个可用区间创建加工变量
                interval_vars = []
                for idx, (avail_start, avail_end) in enumerate(avail_intervals):
                    s = model.NewIntVar(avail_start, avail_end, f"start_job{job_id}_op{op_id}_machine{machine}_int{idx}")
                    e = model.NewIntVar(avail_start, avail_end, f"end_job{job_id}_op{op_id}_machine{machine}_int{idx}")
                    d = model.NewIntVar(0, avail_end - avail_start, f"dur_job{job_id}_op{op_id}_machine{machine}_int{idx}")
                    op_start[(job_id, op_id, machine)].append(s)
                    op_end[(job_id, op_id, machine)].append(e)
                    op_duration[(job_id, op_id, machine)].append(d)

                    # 约束:子加工时段的时长=结束-开始
                    model.Add(e - s == d)
                    # 未选中机器时,子加工时长为0
                    model.Add(d == 0).OnlyEnforceIf(selected[(job_id, op_id)][machine].Not())

                    # 创建区间变量,用于无重叠约束
                    interval = model.NewIntervalVar(s, d, e, f"interval_job{job_id}_op{op_id}_machine{machine}_int{idx}")
                    interval_vars.append(interval)

                # 约束:该机器上所有可用区间的加工时长之和等于任务要求的总时长
                model.Add(sum(op_duration[(job_id, op_id, machine)]) == total_dur)

                # 约束:操作的整体开始时间是所有子加工时段的最早开始时间
                model.AddMinEquality(job_op_start[(job_id, op_id)], op_start[(job_id, op_id, machine)]).OnlyEnforceIf(selected[(job_id, op_id)][machine])
                # 约束:操作的整体结束时间是所有子加工时段的最晚结束时间
                model.AddMaxEquality(job_op_end[(job_id, op_id)], op_end[(job_id, op_id, machine)]).OnlyEnforceIf(selected[(job_id, op_id)][machine])

                # 将子加工区间加入机器的区间列表
                if machine not in machine_intervals:
                    machine_intervals[machine] = []
                machine_intervals[machine].extend(interval_vars)

    # ---------------------- 添加工艺约束(工件操作先后顺序) ----------------------
    for job_id in range(num_jobs):
        for op_id in range(len(jobs[job_id]) - 1):
            # 前一个操作结束后,后一个操作才能开始
            model.Add(job_op_start[(job_id, op_id + 1)] >= job_op_end[(job_id, op_id)])

    # ---------------------- 添加机器无重叠约束 ----------------------
    for machine in range(num_machines):
        if machine in machine_intervals:
            model.AddNoOverlap(machine_intervals[machine])

    # ---------------------- 设置目标函数 ----------------------
    # 完工时间是所有操作结束时间的最大值
    all_op_ends = [job_op_end[(job_id, op_id)] for job_id in range(num_jobs) for op_id in range(len(jobs[job_id]))]
    model.AddMaxEquality(makespan, all_op_ends)
    model.Minimize(makespan)

    # ---------------------- 求解模型 ----------------------
    solver = cp_model.CpSolver()
    solver.parameters.max_time_in_seconds = 60.0  # 设置超时时间
    status = solver.Solve(model)

    # ---------------------- 输出结果 ----------------------
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print(f"最小完工时间: {solver.Value(makespan)}")
        print("\n任务调度详情:")
        for job_id in range(num_jobs):
            print(f"\n工件 {job_id}:")
            for op_id in range(len(jobs[job_id])):
                # 找到选中的机器
                selected_machine = None
                for m in selected[(job_id, op_id)]:
                    if solver.Value(selected[(job_id, op_id)][m]):
                        selected_machine = m
                        break
                assert selected_machine is not None

                start = solver.Value(job_op_start[(job_id, op_id)])
                end = solver.Value(job_op_end[(job_id, op_id)])
                print(f"  操作 {op_id}: 机器 {selected_machine}, 总时段 [{start}, {end}]")
                # 输出各可用区间内的加工子时段
                avail_intervals = machine_available_intervals[selected_machine]
                for idx in range(len(avail_intervals)):
                    d = solver.Value(op_duration[(job_id, op_id, selected_machine)][idx])
                    if d > 0:
                        s = solver.Value(op_start[(job_id, op_id, selected_machine)][idx])
                        e = solver.Value(op_end[(job_id, op_id, selected_machine)][idx])
                        print(f"    子加工时段: [{s}, {e}], 时长 {d}")
    else:
        print("未找到可行解")

if __name__ == "__main__":
    main()

代码关键逻辑说明

  1. 可用区间预处理:将机器的停机区间转换为连续的可用时段,确保任务加工只能落在这些时段内。
  2. 变量分层设计:为操作的选中机器、子加工时段、整体时段分别创建变量,既满足柔性选机需求,又能跟踪任务跨停机的完整调度周期。
  3. 核心约束:
    • 强制任务总加工时长等于要求值,子加工时长之和匹配总时长
    • 子加工时段严格落在机器可用区间内,同一机器同一可用区间内的加工时段无重叠
    • 保证工件操作的先后执行顺序
  4. 目标优化:以所有操作的最晚结束时间为完工时间,通过CP-Sat求解器找到最小值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 23:24:50