一个可以实现多人、自定义项目活动和资源的最优化job shop排程程序。优化目标简化为最大化满足用户排程优先级和时间窗要求,并且支持自定义的目标权重,而不是直接最小化计划总用时。
程序使用开源求解器和建模工具,支持excel或txt表格形式的标准化输入和输出。
业务需求示例
业务需要同时计划多个项目多个站点的共M个活动。站点共有K种场景,每种场景对应一种活动流。每种活动流可以包含不同数目的活动,并且可以定义不同活动间的各种依赖关系(父项-子项可以为多对多的关系)。
另外规定了每种活动对应的资源类型以及对不同资源的不同占用时间。活动和资源间的对应关系也可以是多对多的关系。
资源的容量可以设置上限(比如每人在同一时间段最多可以处理h个活动)。
业务要求所有活动的计划总用时尽量小。在满足先后依赖关系的前提下可以尽量早排或尽量晚排所有活动。
输入格式
输入参数包括:
- planHorizon计划时长。比如如果以天为单位计划全年项目,可以设置planHorizon=365。
- planStrategy排程策略。如果设为"earliest",则靠前的排程得分高,程序会倾向于把活动尽量靠前排。如果设为"latest"则相反。
routing:活动依赖关系
示例:
用数据表示:
task_idx | post_task_idx | interval |
---|---|---|
task0 | task1 | 2 |
task1 | task3 | 1 |
task2 | task3 | 1 |
task3 | task4 | 1 |
task4 | task6 | 1 |
task5 | task6 | 1 |
task6 | taskEnd | 1 |
resource:每项活动占用的资源,以活动ID为行,资源ID为列的透视表格
hierarchy:项目和场景的从属关系
一个项目可以包含多种场景,每种场景可以有多个重复的实例(比如同一个客户项目,场景为新建的有100个,场景为改造的有50个,等等)。如果设置了不同的优先级,优先级值越高,越会在排程中靠前排序。
cost:资源信息
数据检查、优化目标和约束
-
检查资源的最大容量是否能够满足所有活动的资源需求(可以设置multiplier,使资源容量大于几倍资源需求,保证解的可行性)。
-
排程优化会使用以下硬性约束(i.e.如果不能满足,就没有优化结果):
- 每个活动/资源对都必须满足resource表格里定义的资源消耗(活动所需时长)
- 每个活动只能开始一次
- 每个资源的单位时间消耗都必须在上限或以下
- 活动必须满足routing表格里定义的先后依赖关系
- 相邻活动间必须满足routing表格里定义的interval长度
-
排程优化目标:
- 最大化客户给定的排序优先级
建模简述
决策变量:
定义活动数为M,计划时间段为T,先后依赖关系个数为N,资源个数为R。
决策变量是维度为(M, T)的0/1矩阵x,取值为1的位置是每个活动的开始时间,所以x的每行只能有一个1。
约束:
1… 保证x的每行只有一个1:
∑ t T x m t = = 1 , m ∈ 0 , 1 , ⋯   , M \sum_t^T x_{mt}==1, m \in {0,1,\cdots,M} ∑tTxmt==1,m∈0,1,⋯,M
2… 保证每个活动的结束时间都在计划时间段的范围内:
根据每个活动占用资源的最长时间 t m a x t_{max} tmax,设置x的每行最后 t m a x − 1 t_{max}-1 tmax−1个值为0。
3… 建立先后依赖关系:
1). 处理routing中的数据:通过 Job.createTaskDependency 函数,在 DataProblem.precedenceRulesPos, DataProblem.precedenceRulesNeg 两个矩阵中分别存储父项和子项活动。两个矩阵的维度为(N,M), N为先后依赖关系的个数,M为活动个数,前一个矩阵的取值是0/1,后一个矩阵的取值是0/-1。 另外这个函数还计算了依赖关系中父项的时长:
d u r a t i o n n = m a x r ( r e s o u r c e T b l m r ) + i n t e r v a l n duration_n = max_r(resourceTbl_{mr}) + interval_n durationn=maxr(resourceTblmr)+intervaln,其中m为父项index;数组interval是父项和子项间的间隔时间,维度为(N,)。
2). 生成符合各种活动用时的duration矩阵:通过 DataProblem.createResourceRequirement 函数生成多个durationTbl,每个矩阵维度为 (T,T),每一列数据代表时间线上的一个可能的时间段,数值为1的位置为“占用”,0为“空闲”。每个durationTbl代表一种占用时长(比如如果占用时长为2天, d u r a t i o n T b l 2 durationTbl_2 durationTbl2的每列都会有两个数值为1的位置。
3). 计算占用时长:定义活动对资源占用的矩阵为resourceTbl, 维度为(M,R),M为活动个数,R为资源个数。取值为一对活动-资源对的占用时长。
计算约束时,用到两种占用时长。
以资源角度计算的占用时长resourceUsed是维度为(R,T)的矩阵,R为资源个数,T为计划时段。每个值为1的位置对应资源被“占用”,值为0对应“空闲”。每行代表所有活动对这个资源在各个时间点的占用情况:
r e s o u r c e U s e d r t = ∑ m M ∑ j T d u r a t i o n T b l r e s o u r c e T b l r m , t , j ∗ x m j resourceUsed_{rt} = \sum_m^M \sum_j^T durationTbl_{resourceTbl_{rm},t,j} * x_{mj} resourceUsedrt=∑mM∑jTdurationTblresourceTblrm,t,j∗xmj,其中durationTbl选择对应resourceTbl_{rm}的取值。
以活动角度计算的占用时长,用于安排有依赖关系的活动之间的间隔。
首先把先后依赖关系中的父项起始时间转化成时间跨度: