关注我的公众号YueTan进行交流探讨
欢迎关注我的APS仓库:https://github.com/yuetan1988/python-lekin
APS系列入门
- APS入门1-综述
- APS入门2-ortools
- APS入门3-从源码解读一个C# APS项目
- APS入门4: 供应链与APS
- APS入门5:工厂管理
- APS入门6-LEKIN学习与复现
- APS入门7-数字化车间智能排产调度实战
- APS入门8-C++开发-从源码解读一个APS项目
终于来到了实战环节
准备数据
-
job [来自订单]
- 名称,需要的数量,交付日期
- 每个job对应的工艺路线itinerary
-
工艺路线 [来自BOM]
- 每一步的工序
- 每个工序的时常
-
工序
- 每道工序需要的机器,机器的时常/节拍
- 每个工序的用料/单位。损耗率 【计算RPM,开始不必考虑?】
-
机器
- 机器之间的替换关系,或者以机器组的形式包含哪几台机器
- 可以是生产单位,也就是产线
job
工厂建模
数学建模
参数
实体
i
i
i: 表示机器
j
j
j: 表示工件
决策变量
目标
全部工序最小完成时间
保证交付,最少违约
约束
基础
一道工序只能在一个机器上加工
一个机器只能加工一种工序
关键
按工艺路线进行加工
解决方案
在工厂建模和数学建模之后,再加上我们在APS入门6-LEKIN学习与复现中已经完成的Lekin 引擎,可以构建完整的解决方案了。
无限产能
- 直接根据需求日期与单班产能倒推
常见三步
- 根据需求日期倒排,
- 如果最早开始日期超过了当前日期,或前面有空位了,进行正排拉动
- 得到的正排日期进行承诺
Here is a Python code example for the Job Shop Scheduling Problem (JSSP) using backward scheduling:
import heapq
jobs = [(5,3,2), (3,6,5), (6,4,10), (5,8,12), (7,2,4)]
# Each tuple represents a job with (processing_time, release_time, due_date)
num_machines = 3
def backward_scheduling(jobs):
schedule = [[] for _ in range(num_machines)]
jobs.sort(key=lambda x: x[2]) # Sort jobs by decreasing due date
for job in jobs:
processing_time, release_time, due_date = job
assigned = False
for m in range(num_machines-1, -1, -1): # Check machines backwards
if len(schedule[m]) == 0:
schedule[m].append((processing_time, release_time, due_date))
assigned = True
break
else:
last_job = schedule[m][-1]
if release_time >= last_job[1]:
schedule[m].append((processing_time, release_time, due_date))
assigned = True
break
if not assigned:
return None # No feasible schedule
return schedule
schedule = backward_scheduling(jobs)
if schedule is not None:
print(schedule)
else:
print("No feasible schedule found!")
2
When assigning a job, iterate through the compatible machines instead of all machines:
for job in jobs:
processing_time, release_time, due_date, compatible_machines = job
assigned = False
for m in compatible_machines:
# check if job can be scheduled on machine m
...
When checking for overlap, only consider jobs already assigned to that specific machine:
last_job = schedule[m][-1]
if release_time >= last_job[1]:
# schedule job
This allows jobs to be processed on any of their compatible machines. The backward scheduling logic remains the same otherwise.
The full code would look something like:
# jobs with compatible machines
jobs = [((5,3,2), [0,1]), ...]
def backward_scheduling(jobs):
schedule = [[] for _ in range(num_machines)]
for job in jobs:
processing_time, release_time, due_date, compatible_machines = job
assigned = False
for m in compatible_machines:
if len(schedule[m]) == 0:
schedule[m].append(job)
assigned = True
break
else:
last_job = schedule[m][-1]
if release_time >= last_job[1]:
schedule[m].append(job)
assigned = True
break
if not assigned:
return None
return schedule
3
for each job j:
earliest_start = 0
for each operation op in job j:
find earliest compatible machine m that can schedule op after earliest_start
schedule op on machine m
update earliest_start = op.end_time
if any op cannot be scheduled:
return None // infeasible
return schedule
Good question! To handle the case where a single operation can be processed on multiple compatible machines, we just need to modify the machine selection logic in the pseudocode above.
Instead of just selecting the earliest feasible machine, we can:
Find all compatible machines where the operation can be scheduled after the earliest start time.
Evaluate a heuristic to select the best machine from these options.
Some example heuristics:
Choose the machine with the minimum load or length of operations already assigned. This balances load across machines.
Choose the machine that minimizes the start time of the operation. This prioritizes reducing operation start times.
Choose randomly among the feasible options.
The modified pseudocode would be:
for each operation op in job j:
find all compatible machines M where op can be scheduled after earliest_start
m = apply_machine_heuristic(M) // select best machine
schedule op on machine m
update earliest_start
for each operation op in job j:
find all compatible and feasible machines M for op
allocate op processing time across M
// e.g. 60 mins across 2 machines, schedule 30 mins on each
for each machine m in M:
find available time slot on m for portion of op
schedule op portion on m
update earliest_start for next op
import heapq
jobs = [
[(op1, [0,1]), (op2, [1,2]), (op3, [2])],
[(op1, [0,3]), (op2, [1,2]), (op3, [1])]
]
num_machines = 3
def schedule_op(op, compatible_machines, schedule):
portion = op[0] / len(compatible_machines)
for m in compatible_machines:
if len(schedule[m]) == 0:
schedule[m].append((portion, op[1], op[2]))
else:
last_op = schedule[m][-1]
if op[1] >= last_op[2]:
schedule[m].append((portion, op[1], last_op[2] + portion))
op[1] = max(s[-1][2] for s in schedule)
def schedule_jobs(jobs):
schedule = [[] for _ in range(num_machines)]
for job in jobs:
earliest_start = 0
for op in job:
compatible_machines = op[1]
schedule_op(op, compatible_machines, schedule)
earliest_start = op[1]
return schedule
schedule = schedule_jobs(jobs)
print(schedule)
def schedule_op(op, compatible_machines, machine_slots):
portion = op[0] / len(compatible_machines)
for m in compatible_machines:
for slot in machine_slots[m]:
if portion <= slot[1] - slot[0]:
op_start = slot[0]
op_end = op_start + portion
machine_slots[m].remove(slot)
machine_slots[m].append((slot[0], op_start))
machine_slots[m].append((op_end, slot[1]))
break
# Set op start and end times
Here is a Python code example that schedules a set of jobs with multiple operations per job. It handles:
Operations with multiple compatible machines
Splitting operations across compatible machines
Pre-occupied slots on machines
Updating time slots after scheduling
jobs = [
[(op1, 60mins, [0,1]), (op2, 30mins, [1,2])],
[(op1, 45mins, [0,3]), (op2, 20mins, [1,2])]
]
machine_slots = [
[(0, 100), (200, 300)], # Machine 0
[(0,50)], # Machine 1
[] # Machine 2
]
def schedule_jobs(jobs, machine_slots):
for job in jobs:
for op in job:
portion = op[1] / len(op[2]) # Avg portion per machine
for m in op[2]:
for slot in machine_slots[m]:
if slot is occupied:
continue
if portion <= slot[1] - slot[0]:
op_start = slot[0]
op_end = op_start + portion
# Update slots
machine_slots[m].remove(slot)
machine_slots[m].append((slot[0], op_start))
machine_slots[m].append((op_end, slot[1]))
# Schedule portion
schedule[m].append((portion, op_start, op_end))
break
return schedule
schedule = schedule_jobs(jobs, machine_slots)
综合起来
jobs = [
[ (op1, 60mins, [0,1]),
(op2, 30mins, [1,2]),
demand_date = 100
],
[ (op1, 45mins, [0,3]),
(op2, 20mins, [1,2]),
demand_date = 75
]
]
machine_priority = {0: 1, 1: 2, 2: 3}
def schedule_jobs(jobs):
sort_by_demand_date(jobs)
for job in jobs:
total_time = 0
for op in job:
try_machines = op[2]
machine = None
min_time = infinity
for m in try_machines:
projected_time = get_completion_time(op, m)
if projected_time < min_time:
machine = m
min_time = projected_time
schedule_op(op, machine)
total_time += op[1]
job_completion[job] = total_time
def get_completion_time(op, machine):
projected_start = find_slot(op, machine)
priority_factor = machine_priority[machine]
projected_end = projected_start + (op[1] * priority_factor)
return projected_end
schedule = schedule_jobs(jobs)