我正在尝试创建一个具有设备容量检查的模型。我的模型由工作、员工和设备组成,其中员工是工作实体的锚点,这些实体作为链式计划变量实现。每项工作都分配有一名员工和设备。我制作的模型使每个设备都有一个容量,这代表我们拥有的该设备的数量(即容量为 2 的笔意味着我们有 2 支笔)。但是,我坚持对设备进行容量检查,因为任何作业的容量都可能在任何时间点发生变化,因为我们使用的是时间链接模式。
我开始用一种幼稚的方法来计算也有设备的重叠作业的数量,但这是不正确的,因为重叠作业本身可能不会相互重叠,从而给我一个错误的计数并在应该计数时受到惩罚不是。
因此,我尝试了一种不同的方法(如下所示),我得到一对具有相同设备和重叠时间的作业,然后检查所有其他作业以查看第三个作业是否与第三个作业重叠并且具有相同的设备一对。然后,我按员工对这个 TriConstraintStream 进行分组,因此同一员工的重叠工作不会重复计算。这给了我一个员工的 UniConstraintStream,他们的工作与这对重叠,但由于我需要工作的数量,所以我调用另一个 groupBy 将所有员工分组在一起,并创建一个带有计数的 BiConstraintStream。
return factory.forEachUniquePair(Job.class, Joiners.overlapping(Job::getStartDateTime, Job::getEndDateTime))
.filter(((job1, job2) ->
!Collections.disjoint(job1.getEquipment(), job2.getEquipment())))
.join(factory.forEach(Job.class).filter(job -> !job.getEquipment().isEmpty()))
.filter(((job1, job2, job3) ->
job3 != job1 && job3 != job2
// The overlap between jobs 1 and 2 is determined by the later start date and earlier end date.
// job 3 is overlapping if its start is before the overlap's end time...
&& job3.getStartDateTime().isBefore(
(job1.getEndDateTime().isBefore(job2.getEndDateTime()))
? job1.getEndDateTime()
: job2.getEndDateTime())
// and the overlap's start time is before the job's end time.
&& ((job1.getStartDateTime().isAfter(job2.getStartDateTime()))
? job1.getStartDateTime()
: job2.getStartDateTime())
.isBefore(job3.getEndDateTime())
// now check if job3 has equipment that is within both job 1 and 2
&& !Collections.disjoint(job1.getEquipment(), job3.getEquipment())
&& !Collections.disjoint(job2.getEquipment(), job3.getEquipment())))
// group by job3's employee, so employees won't be double-counted
.groupBy(((job1, job2, job3) -> job3.getEmployee()))
// group the employees by their service, and since all employees have a null service, this gives me the count.
.groupBy(Employee::getService, count())
// TODO: equipment capacity is needed so we can determine if it's above capacity or not.
.penalizeLong(HardMediumSoftLongScore.ONE_HARD, (((service, integer) -> (long) (Math.max(0, integer - 2)))))
.asConstraint("Equipment capacity check");
我的第一个问题是:如何将设备纳入此约束中,以便我可以获得其承受惩罚的能力?
我的第二个问题是:这个约束仅在容量大于或等于3时才起作用,因为我一次检查三个作业。有没有一种不那么麻烦的方法可以让我在不检查三个不同作业的情况下检查容量?
我将使用实验性的连续间隔收集器来完成此任务(您需要将实验包复制到项目文件中):
return factory.forEach(Job.class)
// Change Job to (Job, Equipment) tuples
.expand(Job::getEquipment())
.flattenLast(equipment -> equipment)
// Group consecutive intervals by equipment
.groupBy((job, equipment) -> equipment,
ExperimentalConstraintCollectors.consecutiveTemporalInterval(
(job, equipment) -> job,
Job::getStartDateTime,
Job::getEndDateTime)
)
.flattenLast(ConsecutiveIntervalInfo::getIntervalClusters)
// Only include interval clusters that have overlap
.filter((equipment, cluster) -> cluster.hasOverlap())
.expand((equipment, cluster) -> getMaximumOverlap(cluster))
.filter((equipment, cluster, maxOverlapCount) -> maxOverlapCount > equipment.getCapacity())
.penalizeLong(HardMediumSoftLongScore.ONE_HARD,
(equipment, cluster, maxOverlapCount) -> maxOverlapCount - equipment.getCapacity())
.asConstraint("Equipment capacity check");
getMaximumOverlap
是一个跟踪最大开区间数的函数;集群可以迭代:
record JobEvent(LocalDateTime moment, int change) implements Comparable<JobEvent> {
public int compareTo(JobEvent other) {
return moment.compareTo(other.moment);
}
}
int getMaximumOverlap(IntervalCluster<Job, LocalDateTime> cluster) {
List<JobEvent> eventList = new ArrayList<>(2 * cluster.size());
for (var job : cluster) {
eventList.add(new JobEvent(job.getStartDateTime(), 1));
eventList.add(new JobEvent(job.getEndDateTime(), -1));
}
eventList.sort(Comparator.naturalOrder());
int sum = 0;
int maxSum = 0;
for (var event : eventList) {
sum += event.change();
if (sum > maxSum) {
maxSum = sum;
}
}
return maxSum;
}