使用.NET Core与Google Optimization Tools实现加工车间任务规划
前一篇文章《使用.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的最小值,表示生产效率最优的结果。
代码分解
看过本文开头谈到的前一篇文章后,对于项目初始化和相同的基本概念就不再介绍了。
首先定义一些初始化用的值。
// 创建约束求解器.
var solver = new Solver("jobshop");
var machines_count = ;
var jobs_count = ;
var all_machines = Enumerable.Range(, machines_count);
var all_jobs = Enumerable.Range(, jobs_count);
再定义出所有的工序。MakeFixedDurationIntervalVar就是OR-Tools专门用来创建间隔时间的变量类型。
// 将任务拆分成对应的机器和用时的结构
// job 0 = [(0, 3), (1, 2), (2, 2)]
// job 1 = [(0, 2), (2, 1), (1, 4)]
// job 2 = [(1, 4), (2, 3)]
var machines = new int[][]
{
new[] { , , },
new[] { , , },
new[] { , }
}; var processing_times = new int[][]
{
new[] { , , },
new[] {, , },
new[] { , }
}; // 计算总用时
var horizon = ;
foreach (var i in all_jobs)
horizon += processing_times[i].Sum(); // 创建工序变量
var all_tasks = new Dictionary<(int, int), IntervalVar>();
foreach (var i in all_jobs)
{
foreach (var j in Enumerable.Range(, machines[i].Length))
{
all_tasks[(i, j)] = solver.MakeFixedDurationIntervalVar(,
horizon,
processing_times[i][j],
false,
$"Job_{i}_{j}");
}
}
然后定义连接约束和非连接约束,MakeDisjunctiveConstraints专门用来创建非连接约束的,StartsAfterEnd专门用来创建连接约束。
// 创建连接的顺序变量及连接关系
var all_sequences = new SequenceVarVector();
//var all_machines_jobs = new List<IntervalVar>();
foreach (var i in all_machines)
{
var machines_jobs = new IntervalVarVector();
foreach (var j in all_jobs)
{
foreach (var k in Enumerable.Range(, machines[j].Length))
{
if (machines[j][k] == i)
{
machines_jobs.Add(all_tasks[(j, k)]);
}
}
}
var disj = solver.MakeDisjunctiveConstraint(machines_jobs, $"machine {i}");
all_sequences.Add(disj.SequenceVar());
solver.Add(disj);
} // 定义连接约束
foreach (var i in all_jobs)
{
foreach (var j in Enumerable.Range(, machines[i].Length - ))
{
solver.Add(all_tasks[(i, j + )].StartsAfterEnd(all_tasks[(i, j)]));
}
}
重点的部分,就是创建求解目标了。MakeMinimize用来求最小值,第二个参数表示每次移动的步长,直到有解为止。
// 创建求解的极值目标
var end_tasks = new IntVarVector();
foreach (var i in all_jobs)
{
end_tasks.Add(all_tasks[(i, machines[i].Length - )].EndExpr().Var());
}
var obj_var = solver.MakeMax(end_tasks);
var objective_monitor = solver.MakeMinimize(obj_var.Var(), ); // 创建求解的对象
var sequence_phase = solver.MakePhase(all_sequences.ToArray(), Solver.SEQUENCE_DEFAULT);
var vars_phase = solver.MakePhase(new[] { obj_var.Var() }, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
var main_phase = solver.Compose(new[] { sequence_phase, vars_phase });
最后是显示最优解结果的部分。
// 创建最后一个解决方案
var collector = solver.MakeLastSolutionCollector(); // 添加需要关注的变量
collector.Add(all_sequences.ToArray());
collector.AddObjective(obj_var.Var()); foreach (var i in all_machines)
{
var sequence = all_sequences[i];
var sequence_count = sequence.Size();
for (var j = ; j < sequence_count; j++)
{
var t = sequence.Interval(j);
collector.Add(t.StartExpr().Var());
collector.Add(t.EndExpr().Var());
}
} // 显示结果
var disp_col_width = ;
if (solver.Solve(main_phase, new SearchMonitor[] { objective_monitor, collector }))
{
Console.WriteLine("\nOptimal Schedule Length: {0}\n", collector.ObjectiveValue());
var sol_line = "";
var sol_line_tasks = "";
Console.WriteLine("Optimal Schedule\n"); foreach (var i in all_machines)
{
var seq = all_sequences[i];
sol_line += $"Machine {i}: ";
sol_line_tasks += $"Machine {i}: ";
var sequence = collector.ForwardSequence(, seq);
var seq_size = sequence.Count; foreach (var j in Enumerable.Range(, seq_size))
{
var t = seq.Interval(sequence[j]);
sol_line_tasks += t.Name().PadRight(disp_col_width, ' ');
} foreach (var j in Enumerable.Range(, seq_size))
{
var t = seq.Interval(sequence[j]);
var sol_tmp = $"[{collector.Value(0, t.StartExpr().Var())},{collector.Value(0, t.EndExpr().Var())}]";
sol_line += sol_tmp.PadRight(disp_col_width, ' ');
}
sol_line += "\n";
sol_line_tasks += "\n";
}
Console.WriteLine(sol_line_tasks);
Console.WriteLine("Time Intervals for Tasks\n");
Console.WriteLine(sol_line);
}
运行后结果如下:
可以看到,这一次求得了最优解,与前面给的示范的结果不一样了,总时长上更少,是11而不是12了。对应的图解是这样:
是不是觉得很有趣,跃跃欲试了!动手做就是最好的开始。
最后放出完整代码:
using Google.OrTools.ConstraintSolver;
using System;
using System.Collections.Generic;
using System.Linq; public class ConsoleApp1
{
static void Main()
{
// 创建约束求解器.
var solver = new Solver("jobshop");
var machines_count = ;
var jobs_count = ;
var all_machines = Enumerable.Range(, machines_count);
var all_jobs = Enumerable.Range(, jobs_count); // 将任务拆分成对应的机器和用时的结构
// job 0 = [(0, 3), (1, 2), (2, 2)]
// job 1 = [(0, 2), (2, 1), (1, 4)]
// job 2 = [(1, 4), (2, 3)]
var machines = new int[][]
{
new[] { , , },
new[] { , , },
new[] { , }
}; var processing_times = new int[][]
{
new[] { , , },
new[] {, , },
new[] { , }
}; // 计算总用时
var horizon = ;
foreach (var i in all_jobs)
horizon += processing_times[i].Sum(); // 创建工序变量
var all_tasks = new Dictionary<(int, int), IntervalVar>();
foreach (var i in all_jobs)
{
foreach (var j in Enumerable.Range(, machines[i].Length))
{
all_tasks[(i, j)] = solver.MakeFixedDurationIntervalVar(,
horizon,
processing_times[i][j],
false,
$"Job_{i}_{j}");
}
} // 创建连接的顺序变量及连接关系
var all_sequences = new SequenceVarVector();
//var all_machines_jobs = new List<IntervalVar>();
foreach (var i in all_machines)
{
var machines_jobs = new IntervalVarVector();
foreach (var j in all_jobs)
{
foreach (var k in Enumerable.Range(, machines[j].Length))
{
if (machines[j][k] == i)
{
machines_jobs.Add(all_tasks[(j, k)]);
}
}
}
var disj = solver.MakeDisjunctiveConstraint(machines_jobs, $"machine {i}");
all_sequences.Add(disj.SequenceVar());
solver.Add(disj);
} // 定义连接约束
foreach (var i in all_jobs)
{
foreach (var j in Enumerable.Range(, machines[i].Length - ))
{
solver.Add(all_tasks[(i, j + )].StartsAfterEnd(all_tasks[(i, j)]));
}
} // 创建求解的极值目标
var end_tasks = new IntVarVector();
foreach (var i in all_jobs)
{
end_tasks.Add(all_tasks[(i, machines[i].Length - )].EndExpr().Var());
}
var obj_var = solver.MakeMax(end_tasks);
var objective_monitor = solver.MakeMinimize(obj_var.Var(), ); // 创建求解的对象
var sequence_phase = solver.MakePhase(all_sequences.ToArray(), Solver.SEQUENCE_DEFAULT);
var vars_phase = solver.MakePhase(new[] { obj_var.Var() }, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
var main_phase = solver.Compose(new[] { sequence_phase, vars_phase }); // 创建最后一个解决方案
var collector = solver.MakeLastSolutionCollector(); // 添加需要关注的变量
collector.Add(all_sequences.ToArray());
collector.AddObjective(obj_var.Var()); foreach (var i in all_machines)
{
var sequence = all_sequences[i];
var sequence_count = sequence.Size();
for (var j = ; j < sequence_count; j++)
{
var t = sequence.Interval(j);
collector.Add(t.StartExpr().Var());
collector.Add(t.EndExpr().Var());
}
} // 显示结果
var disp_col_width = ;
if (solver.Solve(main_phase, new SearchMonitor[] { objective_monitor, collector }))
{
Console.WriteLine("\nOptimal Schedule Length: {0}\n", collector.ObjectiveValue());
var sol_line = "";
var sol_line_tasks = "";
Console.WriteLine("Optimal Schedule\n"); foreach (var i in all_machines)
{
var seq = all_sequences[i];
sol_line += $"Machine {i}: ";
sol_line_tasks += $"Machine {i}: ";
var sequence = collector.ForwardSequence(, seq);
var seq_size = sequence.Count; foreach (var j in Enumerable.Range(, seq_size))
{
var t = seq.Interval(sequence[j]);
sol_line_tasks += t.Name().PadRight(disp_col_width, ' ');
} foreach (var j in Enumerable.Range(, seq_size))
{
var t = seq.Interval(sequence[j]);
var sol_tmp = $"[{collector.Value(0, t.StartExpr().Var())},{collector.Value(0, t.EndExpr().Var())}]";
sol_line += sol_tmp.PadRight(disp_col_width, ' ');
}
sol_line += "\n";
sol_line_tasks += "\n";
}
Console.WriteLine(sol_line_tasks);
Console.WriteLine("Time Intervals for Tasks\n");
Console.WriteLine(sol_line);
}
}
}
使用.NET Core与Google Optimization Tools实现加工车间任务规划的更多相关文章
- Google Optimization Tools实现加工车间任务规划【Python版】
上一篇介绍了<使用.NET Core与Google Optimization Tools实现加工车间任务规划>,这次将Google官方文档python实现的版本的完整源码献出来,以满足喜爱 ...
- 使用.NET Core与Google Optimization Tools实现员工排班计划Scheduling
上一篇说完<Google Optimization Tools介绍>,让大家初步了解了Google Optimization Tools是一款约束求解(CP)的高效套件.那么我们用.NET ...
- Google Optimization Tools实现员工排班计划Scheduling【Python版】
上一篇介绍了<使用.Net Core与Google Optimization Tools实现员工排班计划Scheduling>,这次将Google官方文档python实现的版本的完整源码献 ...
- Google Optimization Tools介绍
Google Optimization Tools(OR-Tools)是一款专门快速而便携地解决组合优化问题的套件.它包含了: 约束编程求解器. 简单而统一的接口,用于多种线性规划和混合整数规划求解, ...
- Google PageSpeed Tools 性能测试分析
今天给大家介绍下一个工具:Google PageSpeed Tools,根据官方的介绍,简单梳理如下: Page Speed Insights能针对移动设备和电脑设备衡量网页的性能.该工具会抓取网址两 ...
- ASP.NET Core 使用 Google 验证码(reCAPTCHA v3)代替传统验证码
写在前面 友情提示: Google reCAPTCHA(v3下同) 的使用不需要"梯子",但申请账号的时候需要! Google reCAPTCHA 的使用不需要"梯子&q ...
- 在Ubuntu环境下配置NIMH MEG Core Facility之CTF Tools
在Ubuntu环境下配置NIMH MEG Core Facility之CTF Tools 网站有提示: The install script won't work, but you can copy ...
- NET Core 2.1 Global Tools
微软工程师Nate McMaster的博文.NET Core 2.1 Global Tools https://natemcmaster.com/blog/2018/05/12/dotnet-glob ...
- Google performance Tools (gperftools) 使用心得
Google performance Tools (gperftools) 使用心得 gperftools是google开发的一款非常实用的工具集,主要包括:性能优异的malloc free内存分配器 ...
随机推荐
- vue 兼容性——ie家族不支持promise
解决方案: 安装: cnpm install es6-promise 在 main.js 引入 : polyfill require("es6-promise").polyfill ...
- Python基础-python数据类型之元祖、字典(四)
元祖 Python的元组与列表类似,不同之处在于元组的元素不能修改.元组使用小括号,列表使用方括号. tuple=(1,2,3,4) print(tuple) 访问元祖 通过索引访问,也可以进行切片操 ...
- Python开发——变量
变量的作用 把程序运行的中间结果,临时保存到内存里,以备后面的代码继续调用 变量的声明 name = “yuan” 变量的定义规则 1.变量名只能是 字母.数字或下划线的任意组合 2.变量名的第一个 ...
- CSS深入理解之z-index
(http://www.imooc.com/learn/643) 一.z-index基础知识 1.z-index的含义 z-index属性指定了元素及其子元素的[z顺序],而[z顺序]可以决定当元 ...
- .net amr格式文件转换成mp3格式文件的方法
前言:winform端对于音频文件的格式多有限制,大多数不支持amr格式的文件的播放.但是,手机端传过来的音频文件大多数是amr格式的文件,所以,要想在winform客户端支持音频文件的播放,可以通过 ...
- ibatis (六) dynamic的用法
view plain copy print? dynamic可以去除第一个prepend="and"中的字符(这里为and),从而可以帮助你实现一些很实用的功能.具体情况如下: 1 ...
- javascript常见内存泄露
一.全局变量引起的内存泄漏 function func(){ lmw = 123456 //lmw是全局变量,不会被释放 } 二.闭包引起的内存泄漏 function func(){ var lmw ...
- UVaLive 5760 Alice and Bob (博弈 + 记忆化搜索)
题意:有 n 堆石子,有两种操作,一种是从一堆中拿走一个,另一种是把两堆合并起来,Alice 先拿,谁不能拿了谁输,问谁胜. 析:某些堆石子数量为 1 是特殊,石子数量大于 1 个的都合并起来,再拿, ...
- gdb调试多进程多线程程序
一.调试的指令 1.list命令 list linenum 显示程序第linenum行的周围的程序 list function 显示程序名为function的函数的源程序 list 显示当前行后面的源 ...
- Autofac与AOP功能例子
using Autofac.Extras.DynamicProxy; using System; using System.Collections.Generic; using System.Linq ...