前一篇文章《使用.NET Core与Google Optimization Tools实现员工排班计划Scheduling》算是一种针对内容的规划,而针对时间顺序任务规划,加工车间的工活儿是一个典型的场景。在加工车间有不同的工活儿,一般称为作业,每种作业都有多道工序,每道工序只能在特定的机器上完成。工序有不同的时长,而且是不能更改先后的。这些作业正是制造车间大规模生产线的任务,比如汽车零件制造。问题就是,工厂需要做一个最优的规划,使得作业严格按工序进行的前提下,消耗的时间最短,这样就保证了生产效率是最佳的。如果想做到最优规划,以下约束必不可少:

1. 在作业中必须要前一道工序完成才能进行下一道工序。

2. 对于一台机器,一次只能支持一个作业中的一道工序的运转。

3. 对于每道工序,一旦开始就必须完整地结束。

案例背景


以下是某个车间的作业情况,job代表作业,(m,p)代表了工序,其中m表示从0开始的机器编号,p代表了这道工序需要消耗的时长。本文假设了一个作业计划:

job 0 =  [(0, 3), (1, 2), (2, 2)]

job 1 =  [(0, 2), (2, 1), (1, 4)]

job 2 =  [(1, 4), (2, 3)]

如上所示,job 0有三道工序,第一道工序在0号机器用掉3个单位时长,第二道在1号机器用掉2个单位时长,第三道在2号机器用掉2个单位时长,以此类推,总共八道工序。

解决方案


有一种解决方案如下图,在一个时间轴上,每道工序有一个开始时间,占据一定的时长代表消耗部分,互相不会重叠,所有工序安置完毕,最长的地方就是整个作业全部完成的时长。

上图给了一个示范,一共消耗12个单位时长,当然这也不是最优的,后面通过编码我们再计算出最优的结果。

定义约束


首先我们将工序时长定义为task(i, j),表示job i的第j道工序,定义ti, j表示task(i, j)开始的时间点。有了这两种定义,按照之间的要求,于是有了如下的关系约束:

1. 连接约束,对于同一个作业,前一道工序加上消耗的时长就是后一道工序。比如,对于作业job 0来说,t0, 2表示第二道工序开始的位置,最多消耗2个单位时长之后,就是第三道工序的位置,即:t0, 2   + 2  ≤  t0, 3。

2. 非连接约束,对于不同的作业,要保证前一道工序完成后才能进行下一道工序。比如在1号的机器上有task(0, 2)和task(2, 1),它们消耗的时长分别是2和4个单位,那么就有:

t0, 2   + 2  ≤  t2, 1     如果task(0, 2)在task(2, 1)前运行的话

或者

t2, 1   + 4  ≤  t0, 2      如果task(2, 1)在task(0, 2)前运行的话

基于这个关系,前面案例的作业计划的约束关系如图所示:

带箭头的实线表示了每个作业的工序,有连接约束的情况,而虚线表示了非连接约束的情况,实线有箭头是因为每个作业的工序是确定的,而虚线没有箭头也就说明顺序是没有确定的,这也正是我们要通过规划解决的问题。

最终求解目标


如果假定pi, j表示task(i, j)的消耗时长,那么我们要解决的全局问题就是在所有task都完成后,求一个maxi, j  ti, j +  pi, j的最小值,表示生产效率最优的结果。

代码分解


看过本文开头谈到的前一篇文章后,对于项目初始化和相同的基本概念就不再介绍了。

首先定义一些初始化用的值。

  1. // 创建约束求解器.
  2. var solver = new Solver("jobshop");
  3. var machines_count = ;
  4. var jobs_count = ;
  5. var all_machines = Enumerable.Range(, machines_count);
  6. var all_jobs = Enumerable.Range(, jobs_count);

再定义出所有的工序。MakeFixedDurationIntervalVar就是OR-Tools专门用来创建间隔时间的变量类型。

  1. // 将任务拆分成对应的机器和用时的结构
  2. // job 0 = [(0, 3), (1, 2), (2, 2)]
  3. // job 1 = [(0, 2), (2, 1), (1, 4)]
  4. // job 2 = [(1, 4), (2, 3)]
  5. var machines = new int[][]
  6. {
  7. new[] { , , },
  8. new[] { , , },
  9. new[] { , }
  10. };
  11.  
  12. var processing_times = new int[][]
  13. {
  14. new[] { , , },
  15. new[] {, , },
  16. new[] { , }
  17. };
  18.  
  19. // 计算总用时
  20. var horizon = ;
  21. foreach (var i in all_jobs)
  22. horizon += processing_times[i].Sum();
  23.  
  24. // 创建工序变量
  25. var all_tasks = new Dictionary<(int, int), IntervalVar>();
  26. foreach (var i in all_jobs)
  27. {
  28. foreach (var j in Enumerable.Range(, machines[i].Length))
  29. {
  30. all_tasks[(i, j)] = solver.MakeFixedDurationIntervalVar(,
  31. horizon,
  32. processing_times[i][j],
  33. false,
  34. $"Job_{i}_{j}");
  35. }
  36. }

然后定义连接约束和非连接约束,MakeDisjunctiveConstraints专门用来创建非连接约束的,StartsAfterEnd专门用来创建连接约束。

  1. // 创建连接的顺序变量及连接关系
  2. var all_sequences = new SequenceVarVector();
  3. //var all_machines_jobs = new List<IntervalVar>();
  4. foreach (var i in all_machines)
  5. {
  6. var machines_jobs = new IntervalVarVector();
  7. foreach (var j in all_jobs)
  8. {
  9. foreach (var k in Enumerable.Range(, machines[j].Length))
  10. {
  11. if (machines[j][k] == i)
  12. {
  13. machines_jobs.Add(all_tasks[(j, k)]);
  14. }
  15. }
  16. }
  17. var disj = solver.MakeDisjunctiveConstraint(machines_jobs, $"machine {i}");
  18. all_sequences.Add(disj.SequenceVar());
  19. solver.Add(disj);
  20. }
  21.  
  22. // 定义连接约束
  23. foreach (var i in all_jobs)
  24. {
  25. foreach (var j in Enumerable.Range(, machines[i].Length - ))
  26. {
  27. solver.Add(all_tasks[(i, j + )].StartsAfterEnd(all_tasks[(i, j)]));
  28. }
  29. }

重点的部分,就是创建求解目标了。MakeMinimize用来求最小值,第二个参数表示每次移动的步长,直到有解为止。

  1. // 创建求解的极值目标
  2. var end_tasks = new IntVarVector();
  3. foreach (var i in all_jobs)
  4. {
  5. end_tasks.Add(all_tasks[(i, machines[i].Length - )].EndExpr().Var());
  6. }
  7. var obj_var = solver.MakeMax(end_tasks);
  8. var objective_monitor = solver.MakeMinimize(obj_var.Var(), );
  9.  
  10. // 创建求解的对象
  11. var sequence_phase = solver.MakePhase(all_sequences.ToArray(), Solver.SEQUENCE_DEFAULT);
  12. var vars_phase = solver.MakePhase(new[] { obj_var.Var() }, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
  13. var main_phase = solver.Compose(new[] { sequence_phase, vars_phase });

最后是显示最优解结果的部分。

  1. // 创建最后一个解决方案
  2. var collector = solver.MakeLastSolutionCollector();
  3.  
  4. // 添加需要关注的变量
  5. collector.Add(all_sequences.ToArray());
  6. collector.AddObjective(obj_var.Var());
  7.  
  8. foreach (var i in all_machines)
  9. {
  10. var sequence = all_sequences[i];
  11. var sequence_count = sequence.Size();
  12. for (var j = ; j < sequence_count; j++)
  13. {
  14. var t = sequence.Interval(j);
  15. collector.Add(t.StartExpr().Var());
  16. collector.Add(t.EndExpr().Var());
  17. }
  18. }
  19.  
  20. // 显示结果
  21. var disp_col_width = ;
  22. if (solver.Solve(main_phase, new SearchMonitor[] { objective_monitor, collector }))
  23. {
  24. Console.WriteLine("\nOptimal Schedule Length: {0}\n", collector.ObjectiveValue());
  25. var sol_line = "";
  26. var sol_line_tasks = "";
  27. Console.WriteLine("Optimal Schedule\n");
  28.  
  29. foreach (var i in all_machines)
  30. {
  31. var seq = all_sequences[i];
  32. sol_line += $"Machine {i}: ";
  33. sol_line_tasks += $"Machine {i}: ";
  34. var sequence = collector.ForwardSequence(, seq);
  35. var seq_size = sequence.Count;
  36.  
  37. foreach (var j in Enumerable.Range(, seq_size))
  38. {
  39. var t = seq.Interval(sequence[j]);
  40. sol_line_tasks += t.Name().PadRight(disp_col_width, ' ');
  41. }
  42.  
  43. foreach (var j in Enumerable.Range(, seq_size))
  44. {
  45. var t = seq.Interval(sequence[j]);
  46. var sol_tmp = $"[{collector.Value(0, t.StartExpr().Var())},{collector.Value(0, t.EndExpr().Var())}]";
  47. sol_line += sol_tmp.PadRight(disp_col_width, ' ');
  48. }
  49. sol_line += "\n";
  50. sol_line_tasks += "\n";
  51. }
  52. Console.WriteLine(sol_line_tasks);
  53. Console.WriteLine("Time Intervals for Tasks\n");
  54. Console.WriteLine(sol_line);
  55. }

运行后结果如下:

可以看到,这一次求得了最优解,与前面给的示范的结果不一样了,总时长上更少,是11而不是12了。对应的图解是这样:

是不是觉得很有趣,跃跃欲试了!动手做就是最好的开始。

最后放出完整代码:

  1. using Google.OrTools.ConstraintSolver;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5.  
  6. public class ConsoleApp1
  7. {
  8. static void Main()
  9. {
  10. // 创建约束求解器.
  11. var solver = new Solver("jobshop");
  12. var machines_count = ;
  13. var jobs_count = ;
  14. var all_machines = Enumerable.Range(, machines_count);
  15. var all_jobs = Enumerable.Range(, jobs_count);
  16.  
  17. // 将任务拆分成对应的机器和用时的结构
  18. // job 0 = [(0, 3), (1, 2), (2, 2)]
  19. // job 1 = [(0, 2), (2, 1), (1, 4)]
  20. // job 2 = [(1, 4), (2, 3)]
  21. var machines = new int[][]
  22. {
  23. new[] { , , },
  24. new[] { , , },
  25. new[] { , }
  26. };
  27.  
  28. var processing_times = new int[][]
  29. {
  30. new[] { , , },
  31. new[] {, , },
  32. new[] { , }
  33. };
  34.  
  35. // 计算总用时
  36. var horizon = ;
  37. foreach (var i in all_jobs)
  38. horizon += processing_times[i].Sum();
  39.  
  40. // 创建工序变量
  41. var all_tasks = new Dictionary<(int, int), IntervalVar>();
  42. foreach (var i in all_jobs)
  43. {
  44. foreach (var j in Enumerable.Range(, machines[i].Length))
  45. {
  46. all_tasks[(i, j)] = solver.MakeFixedDurationIntervalVar(,
  47. horizon,
  48. processing_times[i][j],
  49. false,
  50. $"Job_{i}_{j}");
  51. }
  52. }
  53.  
  54. // 创建连接的顺序变量及连接关系
  55. var all_sequences = new SequenceVarVector();
  56. //var all_machines_jobs = new List<IntervalVar>();
  57. foreach (var i in all_machines)
  58. {
  59. var machines_jobs = new IntervalVarVector();
  60. foreach (var j in all_jobs)
  61. {
  62. foreach (var k in Enumerable.Range(, machines[j].Length))
  63. {
  64. if (machines[j][k] == i)
  65. {
  66. machines_jobs.Add(all_tasks[(j, k)]);
  67. }
  68. }
  69. }
  70. var disj = solver.MakeDisjunctiveConstraint(machines_jobs, $"machine {i}");
  71. all_sequences.Add(disj.SequenceVar());
  72. solver.Add(disj);
  73. }
  74.  
  75. // 定义连接约束
  76. foreach (var i in all_jobs)
  77. {
  78. foreach (var j in Enumerable.Range(, machines[i].Length - ))
  79. {
  80. solver.Add(all_tasks[(i, j + )].StartsAfterEnd(all_tasks[(i, j)]));
  81. }
  82. }
  83.  
  84. // 创建求解的极值目标
  85. var end_tasks = new IntVarVector();
  86. foreach (var i in all_jobs)
  87. {
  88. end_tasks.Add(all_tasks[(i, machines[i].Length - )].EndExpr().Var());
  89. }
  90. var obj_var = solver.MakeMax(end_tasks);
  91. var objective_monitor = solver.MakeMinimize(obj_var.Var(), );
  92.  
  93. // 创建求解的对象
  94. var sequence_phase = solver.MakePhase(all_sequences.ToArray(), Solver.SEQUENCE_DEFAULT);
  95. var vars_phase = solver.MakePhase(new[] { obj_var.Var() }, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
  96. var main_phase = solver.Compose(new[] { sequence_phase, vars_phase });
  97.  
  98. // 创建最后一个解决方案
  99. var collector = solver.MakeLastSolutionCollector();
  100.  
  101. // 添加需要关注的变量
  102. collector.Add(all_sequences.ToArray());
  103. collector.AddObjective(obj_var.Var());
  104.  
  105. foreach (var i in all_machines)
  106. {
  107. var sequence = all_sequences[i];
  108. var sequence_count = sequence.Size();
  109. for (var j = ; j < sequence_count; j++)
  110. {
  111. var t = sequence.Interval(j);
  112. collector.Add(t.StartExpr().Var());
  113. collector.Add(t.EndExpr().Var());
  114. }
  115. }
  116.  
  117. // 显示结果
  118. var disp_col_width = ;
  119. if (solver.Solve(main_phase, new SearchMonitor[] { objective_monitor, collector }))
  120. {
  121. Console.WriteLine("\nOptimal Schedule Length: {0}\n", collector.ObjectiveValue());
  122. var sol_line = "";
  123. var sol_line_tasks = "";
  124. Console.WriteLine("Optimal Schedule\n");
  125.  
  126. foreach (var i in all_machines)
  127. {
  128. var seq = all_sequences[i];
  129. sol_line += $"Machine {i}: ";
  130. sol_line_tasks += $"Machine {i}: ";
  131. var sequence = collector.ForwardSequence(, seq);
  132. var seq_size = sequence.Count;
  133.  
  134. foreach (var j in Enumerable.Range(, seq_size))
  135. {
  136. var t = seq.Interval(sequence[j]);
  137. sol_line_tasks += t.Name().PadRight(disp_col_width, ' ');
  138. }
  139.  
  140. foreach (var j in Enumerable.Range(, seq_size))
  141. {
  142. var t = seq.Interval(sequence[j]);
  143. var sol_tmp = $"[{collector.Value(0, t.StartExpr().Var())},{collector.Value(0, t.EndExpr().Var())}]";
  144. sol_line += sol_tmp.PadRight(disp_col_width, ' ');
  145. }
  146. sol_line += "\n";
  147. sol_line_tasks += "\n";
  148. }
  149. Console.WriteLine(sol_line_tasks);
  150. Console.WriteLine("Time Intervals for Tasks\n");
  151. Console.WriteLine(sol_line);
  152. }
  153. }
  154. }

使用.NET Core与Google Optimization Tools实现加工车间任务规划的更多相关文章

  1. Google Optimization Tools实现加工车间任务规划【Python版】

    上一篇介绍了<使用.NET Core与Google Optimization Tools实现加工车间任务规划>,这次将Google官方文档python实现的版本的完整源码献出来,以满足喜爱 ...

  2. 使用.NET Core与Google Optimization Tools实现员工排班计划Scheduling

    上一篇说完<Google Optimization Tools介绍>,让大家初步了解了Google Optimization Tools是一款约束求解(CP)的高效套件.那么我们用.NET ...

  3. Google Optimization Tools实现员工排班计划Scheduling【Python版】

    上一篇介绍了<使用.Net Core与Google Optimization Tools实现员工排班计划Scheduling>,这次将Google官方文档python实现的版本的完整源码献 ...

  4. Google Optimization Tools介绍

    Google Optimization Tools(OR-Tools)是一款专门快速而便携地解决组合优化问题的套件.它包含了: 约束编程求解器. 简单而统一的接口,用于多种线性规划和混合整数规划求解, ...

  5. Google PageSpeed Tools 性能测试分析

    今天给大家介绍下一个工具:Google PageSpeed Tools,根据官方的介绍,简单梳理如下: Page Speed Insights能针对移动设备和电脑设备衡量网页的性能.该工具会抓取网址两 ...

  6. ASP.NET Core 使用 Google 验证码(reCAPTCHA v3)代替传统验证码

    写在前面 友情提示: Google reCAPTCHA(v3下同) 的使用不需要"梯子",但申请账号的时候需要! Google reCAPTCHA 的使用不需要"梯子&q ...

  7. 在Ubuntu环境下配置NIMH MEG Core Facility之CTF Tools

    在Ubuntu环境下配置NIMH MEG Core Facility之CTF Tools 网站有提示: The install script won't work, but you can copy ...

  8. NET Core 2.1 Global Tools

    微软工程师Nate McMaster的博文.NET Core 2.1 Global Tools https://natemcmaster.com/blog/2018/05/12/dotnet-glob ...

  9. Google performance Tools (gperftools) 使用心得

    Google performance Tools (gperftools) 使用心得 gperftools是google开发的一款非常实用的工具集,主要包括:性能优异的malloc free内存分配器 ...

随机推荐

  1. fabric 持久化

    每个容器都有目录需要映射出来.在volume中添加如下映射即可: peer是: /var/hyperledger/peer{number}/org{number}:/var/hyperledger/p ...

  2. 287. Find the Duplicate Number 找出数组中的重复数字

    [抄题]: Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive ...

  3. 一 分析easyswoole源码(启动服务)

    分析easyswoole源码 1以启动为例 //检查是否已经安装 installCheck();//检查锁文件是否存在,不存在结束 //启动服务 serverStart showLogo();//显示 ...

  4. Pandas处理丢失数据

    1.创建含NaN的矩阵 >>> dates = pd.date_range(', periods=6) >>> df = pd.DataFrame(np.arang ...

  5. Nginx学习笔记(反向代理&搭建集群)

    一.前言 1.1 大型互联网架构演变历程 1.1.1 淘宝技术 淘宝的核心技术(国内乃至国际的 Top,这还是2011年的数据) 拥有全国最大的分布式 Hadoop 集群(云梯,2000左右节点,24 ...

  6. C语言基础课第五次作业

    PTA第五次作业 7-2 统计一行文本中的单词个数 一.程序代码 #include<stdio.h> int main(void) { ]; ,word=; char c; gets(st ...

  7. PHP 百万级数据导出方案(多 CSV 文件压缩)

    ps:来源 :https://laravel-china.org/articles/15944/php-million-level-data-export-scheme-multi-csv-file- ...

  8. Codeforces Round #539 (Div. 2) 异或 + dp

    https://codeforces.com/contest/1113/problem/C 题意 一个n个数字的数组a[],求有多少对l,r满足\(sum[l,mid]=sum[mid+1,r]\), ...

  9. leetcode437--Path Sum III

    https://leetcode.com/problems/path-sum-iii/ 理解比较困难,可以先看https://www.cnblogs.com/albert67/p/10416402.h ...

  10. HTML之<meta>使用和说明

    关于<meta>,我们都不陌生,随意打开一个网页查看源代码就可以看到<head>里出现它的身影. 简单来说,<meta>是描述 HTML 文档的元数据.例如网页描述 ...