Local Solver求解JobShop问题
学习的原则:
- 添加多个列表决策变量
- 限制列表中的元素数量
- 通过将数值决策变量与列表变量配对来对它们进行排序
问题
必须在车间的每台机器上处理一组作业。每个作业都包含一个有序的任务序列(称为活动),每个任务代表在一台机器上对作业的处理。每个作业每台计算机都有一个活动,并且在作业的上一个活动未完成时无法启动活动。每个活动都有给定的处理时间,每台机器一次只能处理一个活动。
目的是要找到一系列可最大程度缩短生产时间的作业:处理完所有作业的时间。
数据
提供的实例遵循Taillard格式。数据文件的格式如下:
第一行:作业数,机器数,用于生成实例的种子,之前找到的上限和下限。
对于每个作业:每台机器上的处理时间(按处理顺序给出)。
对于每个作业:处理顺序(访问机器的排序列表)。
程序
我们使用整数决策变量来模拟活动的开始时间。结束时间表达式是通过将每个活动的开始时间和处理时间相加得出的。
优先级约束很容易编写:对于每个作业,该作业的任何活动都必须在前一台机器处理的活动结束后才开始。
除了代表活动开始时间的整数决策外,我们还使用列表决策变量(list decision variables)。如在Flowshop示例中一样,一个列表对机器内活动的顺序进行建模。多亏了“Count”运算符,我们限制了每台机器上要处理的所有作业。
可区分的资源约束(每台机器一次只能处理一个活动)可以重新定义如下:给定一系列作业,与任何作业相对应的活动必须在与先前作业相对应的活动结束后才开始。
为了对这些约束进行建模,我们将整数决策(开始时间)与列表决策(工作顺序)配对。我们编写一个lambda函数,表示两个连续活动的开始时间之间的关系。该功能在“and”运算符中使用用于机器处理的所有活动。
要最小化的完成时间是处理完所有活动的时间。
如有疑问或申请试用,请联系LocalSolver中国授权代理商无锡迅合信息科技有限公司 xunhetech.com 。
Compilation / Execution (Windows)
copy %LS_HOME%\bin\localsolvernet.dll .
csc Jobshop.cs /reference:localsolvernet.dll
jobshop instances\ft10.txt
/********** JobShop.cs **********/
using System;
using System.IO;
using localsolver;
public class Jobshop : IDisposable
{
// Number of jobs
private int nbJobs;
// Number of machines
private int nbMachines;
// Processing time on each machine for each job (given in the machine order)
private long[,] processingTime;
// Processing order of machines for each job
private int[,] machineOrder;
// Trivial upper bound for the start times of the activities
private long maxStart;
// LocalSolver
private LocalSolver localsolver;
// Decision variables: start times of the activities
private LSExpression[,] start;
// Decision variables: sequence of activities on each machine
private LSExpression[] jobsOrder;
// Objective = minimize the makespan: end of the last activity of the last job
private LSExpression makespan;
public Jobshop(string fileName)
{
localsolver = new LocalSolver();
ReadInstance(fileName);
}
// The input files follow the "Taillard" format
private void ReadInstance(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
input.ReadLine();
string[] splitted = input.ReadLine().Split(' ');
nbJobs = int.Parse(splitted[0]);
nbMachines = int.Parse(splitted[1]);
// Processing times for each job on each machine (given in the processing order)
input.ReadLine();
long[,] processingTimesInProcessingOrder = new long[nbJobs, nbMachines];
for (int j = 0; j < nbJobs; j++)
{
splitted = input.ReadLine().Trim().Split(' ');
for (int m = 0; m < nbMachines; m++)
{
processingTimesInProcessingOrder[j, m] = long.Parse(splitted[m]);
}
}
// Processing order of machines for each job
input.ReadLine();
machineOrder = new int[nbJobs, nbMachines];
for (int j = 0; j < nbJobs; j++)
{
splitted = input.ReadLine().Trim().Split(' ');
for (int m = 0; m < nbMachines; m++)
{
machineOrder[j, m] = int.Parse(splitted[m]) - 1;
}
}
// Reorder processing times: processingTime[j, m] is the processing time of the
// activity of job j that is processed on machine m
processingTime = new long[nbJobs, nbMachines];
// Trivial upper bound for the start times of the activities
maxStart = 0;
for (int j = 0; j < nbJobs; j++)
{
for (int m = 0; m < nbMachines; m++)
{
int machineIndex = nbMachines;
for (int k = 0; k < nbMachines; k++)
{
if (machineOrder[j, k] == m) {
machineIndex = k;
break;
}
}
processingTime[j, m] = processingTimesInProcessingOrder[j, machineIndex];
maxStart += processingTime[j, m];
}
}
}
}
public void Dispose()
{
localsolver.Dispose();
}
public void Solve(int timeLimit)
{
// Declares the optimization model
LSModel model = localsolver.GetModel();
// Integer decisions: start time of each activity
// start[j, m] is the start time of the activity of job j which is processed on machine m
start = new LSExpression[nbJobs, nbMachines];
LSExpression[,] end = new LSExpression[nbJobs, nbMachines];
for (int j = 0; j < nbJobs; j++)
{
for (int m = 0; m < nbMachines; m++)
{
start[j, m] = model.Int(0, maxStart);
end[j, m] = start[j, m] + processingTime[j, m];
}
}
LSExpression startArray = model.Array(start);
LSExpression endArray = model.Array(end);
// Precedence constraints between the activities of a job
for (int j = 0; j < nbJobs; j++)
{
for (int k = 1; k < nbMachines; k++)
{
model.Constraint(start[j, machineOrder[j, k]] >= end[j, machineOrder[j, k-1]]);
}
}
// Sequence of activities on each machine
jobsOrder = new LSExpression[nbMachines];
for (int m = 0; m < nbMachines; m++)
{
jobsOrder[m] = model.List(nbJobs);
}
for (int m = 0; m < nbMachines; m++)
{
// Each job has an activity scheduled on each machine
LSExpression sequence = jobsOrder[m];
model.Constraint(model.Count(sequence) == nbJobs);
// Disjunctive resource constraints between the activities on a machine
LSExpression sequenceSelector = model.LambdaFunction(i => startArray[sequence[i], m]
>= endArray[sequence[i-1], m]);
model.Constraint(model.And(model.Range(1, nbJobs), sequenceSelector));
}
// Minimize the makespan: end of the last activity of the last job
makespan = model.Max();
for (int j = 0; j < nbJobs; j++)
{
makespan.AddOperand(end[j, machineOrder[j, nbMachines-1]]);
}
model.Minimize(makespan);
model.Close();
// Parameterizes the solver
localsolver.GetParam().SetTimeLimit(timeLimit);
localsolver.Solve();
}
// Writes the solution in a file with the following format:
// - for each machine, the job sequence
public void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName)) {
Console.WriteLine("Solution written in file " + fileName);
for (int m = 0; m < nbMachines; m++)
{
LSCollection finalJobsOrder = jobsOrder[m].GetCollectionValue();
for (int i = 0; i < nbJobs; i++)
{
int j = (int) finalJobsOrder.Get(i);
output.Write(j + " ");
}
output.WriteLine();
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine ("Usage: Jobshop instanceFile [outputFile] [timeLimit]");
System.Environment.Exit(1);
}
string instanceFile = args [0];
string outputFile = args.Length > 1 ? args[1] : null;
string strTimeLimit = args.Length > 2 ? args[2] : "60";
using (Jobshop model = new Jobshop(instanceFile))
{
model.Solve (int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
}