我正在处理作业车间调度问题,其中我有一些定义为时间间隔变量的任务:
IntervalVar taskInterval = model.NewIntervalVar(start, duration, end, $"interval_{task.WorkOrder_Id}_{task.TaskId}");
每个任务都可以在某个工作站上完成,每个工作站都有自己的日历。
List<UserCalendarsDTO> userCalendarsDTOs = userCalendars.Where(x => x.WorkProviderCode == task.UserWorkStation & x.IdCategory == CalendarGroupCategoryEnum.Available). OrderBy(x => x.Date).ToList();//we can only planfor (int calendarID = 0; calendarID < userCalendarsDTOs.Count() - 1; ++calendarID){ //add daily constraints...
因此,今天我只能期望工作站 A 有 1 项任务,而明天我希望看到 2 项任务。
我正在查看 AddCumulative 约束,但在我看来,它只能用于 IntervalVar 的静态集合。这并不令人满意,因为我不知道任务何时开始(今天或明天,所以容量必须是 1 或 2)。我查看了问题 OR-tools:
此外,C# 中 AddCumulative 的用法与 Python 中的不同,因此如果能提供相关参考资料,我将不胜感激。
我曾尝试将时间间隔动态添加到集合中,但我没有找到任何方法或工具函数可以做到这一点。
说的对,AddCumulative
约束在 C# 中需要一个静态的间隔变量集。但是,可以使用它来模拟随时间变化的容量,方法是为每个时间步创建一个“虚拟”间隔变量,并使用它来调整该时间步的总容量。
以下是如何使用 C# 和 Google OR-Tools 实现这一点的示例:
using Google.OrTools.Sat;
public class JobShopScheduling
{
public static void Solve()
{
// 数据
var tasks = new List<(int WorkStation, int Duration, int Day)>
{
(0, 1, 0), // 工作站 0,持续时间 1,第 0 天
(0, 1, 1), // 工作站 0,持续时间 1,第 1 天
(1, 2, 0), // 工作站 1,持续时间 2,第 0 天
};
var numWorkstations = 2;
var horizon = 2; // 规划范围(天数)
// 创建模型
var model = new CpModel();
// 创建间隔变量
var taskIntervals = new IntervalVar[tasks.Count];
for (int i = 0; i < tasks.Count; i++)
{
var (workStation, duration, day) = tasks[i];
var start = model.NewIntVar(day, horizon - 1, $"start_{i}");
var end = model.NewIntVar(day, horizon, $"end_{i}");
taskIntervals[i] = model.NewIntervalVar(start, duration, end, $"interval_{i}");
}
// 创建虚拟间隔变量以模拟动态容量
var capacityIntervals = new IntervalVar[numWorkstations, horizon];
for (int w = 0; w < numWorkstations; w++)
{
for (int t = 0; t < horizon; t++)
{
// 获取该时间步的容量
var capacity = GetCapacity(w, t); // 此函数返回时间步 t 工作站 w 的容量
// 创建虚拟间隔变量
var start = model.NewConstant(t);
var end = model.NewConstant(t + 1);
var interval = model.NewOptionalIntervalVar(start, 1, end, true, $"capacity_{w}_{t}");
// 如果容量大于 0,则强制虚拟间隔变量为 present
model.Add(interval.PresenceLiteral == (capacity > 0));
// 将虚拟间隔变量添加到列表中
capacityIntervals[w, t] = interval;
}
}
// 为每个工作站添加累积约束
for (int w = 0; w < numWorkstations; w++)
{
// 收集该工作站的所有任务间隔变量
var workstationTasks = taskIntervals.Where((interval, i) => tasks[i].WorkStation == w).ToList();
// 创建一个包含所有相关间隔变量的列表
var allIntervals = workstationTasks.Concat(Enumerable.Range(0, horizon).Select(t => capacityIntervals[w, t])).ToList();
// 获取每个间隔变量的容量
var demands = workstationTasks.Select(_ => 1).Concat(Enumerable.Range(0, horizon).Select(t => -GetCapacity(w, t))).ToList();
// 添加累积约束
model.AddCumulative(allIntervals.ToArray(), demands.ToArray(), 0, $"cumulative_{w}");
}
// 定义目标函数(例如,最小化 makespan)
var makespan = model.NewIntVar(0, horizon, "makespan");
model.AddMaxEquality(makespan, taskIntervals.Select(interval => interval.EndExpr).ToArray());
model.Minimize(makespan);
// 创建求解器并求解模型
var solver = new CpSolver();
var status = solver.Solve(model);
// 打印解决方案
if (status == CpSolverStatus.Optimal || status == CpSolverStatus.Feasible)
{
Console.WriteLine("Solution:");
for (int i = 0; i < tasks.Count; i++)
{
Console.WriteLine($" Task {i}: start = {solver.Value(taskIntervals[i].StartExpr)}, end = {solver.Value(taskIntervals[i].EndExpr)}");
}
Console.WriteLine($"Makespan: {solver.Value(makespan)}");
}
else
{
Console.WriteLine("No solution found.");
}
}
// 此函数返回时间步 t 工作站 w 的容量
private static int GetCapacity(int workstation, int timestep)
{
// 在此处实现的逻辑以根据工作站和时间步检索容量
// 例如,可以从数据库或文件中读取容量数据
// 示例:
if (workstation == 0 && timestep == 0) return 1; // 工作站 0 在第 0 天的容量为 1
if (workstation == 0 && timestep == 1) return 2; // 工作站 0 在第 1 天的容量为 2
if (workstation == 1) return 1; // 工作站 1 的容量始终为 1
return 0;
}
}
在此示例中,我们创建了额外的虚拟间隔变量 capacityIntervals
来表示每个时间步的容量。这些间隔变量的持续时间为 1,并且如果该时间步的容量大于 0,则强制它们为 present。然后,我们使用 AddCumulative
约束来确保每个时间步使用的总容量不超过可用容量。
GetCapacity
函数用于检索每个时间步每个工作站的容量。可以根据的特定用例调整此函数。
这种方法允许使用 AddCumulative
约束来模拟随时间变化的容量,即使 C# 中的约束需要一个静态的间隔变量集也是如此。