如何解决时间表的平均分配
我在 optaplanner 中有一个系统,可以为员工生成轮班。当有 e.x 6 名员工且轮班容量为 2 时,仅为 2 名员工生成解决方案,直到他们达到最大工作时间。我如何添加约束以便解决方案由混合员工生成。
以下是 optaplanner 中定义的规则:
global HardMediumSoftLongscoreHolder scoreHolder;
// Hard constraints
rule "required skill for a shift"
when
$shift: Shift(employee != null,hasrequiredSkills() == false)
then
scoreHolder.penalize(kcontext,$shift.getLengthInMinutes());
end
rule "Unavailable time slot for an employee"
when
$availability: EmployeeAvailability(
state == EmployeeAvailabilityState.UNAVAILABLE,$e : employee,$startDateTime : startDateTime,$endDateTime : endDateTime)
Shift(employee == $e,$startDateTime < endDateTime,$endDateTime > startDateTime)
then
scoreHolder.penalize(kcontext,$availability.getDuration().toMinutes());
end
rule "No overlapping shifts"
when
$s : Shift(employee != null,$firstStartDateTime: startDateTime,$firstEndDateTime : endDateTime)
$s2: Shift(employee == $e,this != $s,$firstStartDateTime < endDateTime,$firstEndDateTime > startDateTime)
then
scoreHolder.penalize(kcontext,$s2.getLengthInMinutes());
end
rule "No more than 2 consecutive shifts"
when
$s : Shift(
employee != null,$firstEndDateTime : endDateTime)
$s2: Shift(
employee == $e,$firstEndDateTime == startDateTime,$secondEndDateTime : endDateTime)
$s3: Shift(
employee == $e,$secondEndDateTime == startDateTime,this != $s2)
then
scoreHolder.penalize(kcontext,$s3.getLengthInMinutes());
end
rule "Break between non-consecutive shifts is at least 10 hours"
when
$s : Shift(
employee != null,$leftEndDateTime : endDateTime)
Shift(
employee == $e,$leftEndDateTime < startDateTime,$leftEndDateTime.until(startDateTime,ChronoUnit.HOURS) < 10,$rightStartDateTime: startDateTime)
then
long breakLength = $leftEndDateTime.until($rightStartDateTime,ChronoUnit.MINUTES);
scoreHolder.penalize(kcontext,(10 * 60) - breakLength);
end
rule "Daily minutes must not exceed contract maximum"
when
$employee : Employee($contract : contract,$contract.getMaximumMinutesPerDay() != null)
$s : Shift(employee == $employee,$startDateTime : startDateTime)
accumulate(
$other : Shift(
employee == $employee,$shiftStart : startDateTime,$shiftEnd : endDateTime,$shiftStart.toLocalDate().equals($startDateTime.toLocalDate())
),$shiftCount : count($other),$totalMinutes : sum(Duration.between($shiftStart,$shiftEnd).toMinutes())
)
Number(this > $contract.getMaximumMinutesPerDay()) from $totalMinutes
then
scoreHolder.penalize(kcontext,(((long)$totalMinutes) - $contract.getMaximumMinutesPerDay()) / $shiftCount);
end
rule "Weekly minutes must not exceed contract maximum"
when
$rosterConstraintConfiguration : RosterConstraintConfiguration()
$employee : Employee($contract : contract,$contract.getMaximumMinutesPerWeek() != null)
$s : Shift(employee == $employee,DateTimeUtils.sameWeek($rosterConstraintConfiguration.getWeekStartDay(),$shiftStart,$startDateTime)
),$shiftEnd).toMinutes())
)
Number(this > $contract.getMaximumMinutesPerWeek()) from $totalMinutes
then
scoreHolder.penalize(kcontext,(((long)$totalMinutes) - $contract.getMaximumMinutesPerWeek()) / $shiftCount);
end
rule "Monthly minutes must not exceed contract maximum"
when
$employee : Employee($contract : contract,$contract.getMaximumMinutesPerMonth() != null)
$s : Shift(employee == $employee,$shiftStart.getMonth() == $startDateTime.getMonth(),$shiftStart.getYear() == $startDateTime.getYear()
),$shiftEnd).toMinutes())
)
Number(this > $contract.getMaximumMinutesPerMonth()) from $totalMinutes
then
scoreHolder.penalize(kcontext,(((long)$totalMinutes) - $contract.getMaximumMinutesPerMonth()) / $shiftCount);
end
rule "Yearly minutes must not exceed contract maximum"
when
$employee : Employee($contract : contract,$contract.getMaximumMinutesPerYear() != null)
$s : Shift(employee == $employee,$shiftEnd).toMinutes())
)
Number(this > $contract.getMaximumMinutesPerYear()) from $totalMinutes
then
scoreHolder.penalize(kcontext,(((long)$totalMinutes) - $contract.getMaximumMinutesPerYear()) / $shiftCount);
end
// Medium constraints
rule "Assign every shift"
when
Shift(employee == null)
then
scoreHolder.penalize(kcontext);
end
// Soft constraints
rule "Employee is not original employee"
when
$shift: Shift(originalEmployee != null,employee != null,employee != originalEmployee)
then
scoreHolder.penalize(kcontext,$shift.getLengthInMinutes());
end
rule "Undesired time slot for an employee"
when
$availability: EmployeeAvailability(
state == EmployeeAvailabilityState.UNDESIRED,$availability.getDuration().toMinutes());
end
rule "Desired time slot for an employee"
when
$availability: EmployeeAvailability(
state == EmployeeAvailabilityState.DESIRED,$endDateTime > startDateTime)
then
scoreHolder.reward(kcontext,$availability.getDuration().toMinutes());
end
rule "Employee is not rotation employee"
when
$shift: Shift(rotationEmployee != null,employee != rotationEmployee)
then
scoreHolder.penalize(kcontext,$shift.getLengthInMinutes());
end
解决方法
您应该在求解器配置文件中添加此配置
<localSearch>
<unionMoveSelector>
<changeMoveSelector>
<selectionOrder>RANDOM</selectionOrder>
</changeMoveSelector>
</unionMoveSelector>
</localSearch>
,
可能是你的时间跨度太短了。假设它不是...
您应该制定一个对工作负载公平分配进行评分的约束。如果这 2 名员工完成所有工作的 100%,而其他 4 名员工完成所有工作的 0%,则存在“公平”问题。
举个例子,假设总共有 12 个工作单元要分配给这 6 名员工。即使这 12 个单位不超过年/月/周,甚至没有超过每日员工合同的约束或您制定的任何其他约束,那么额外的约束将确保 12 个工作单位将公平地分配到6 名员工,因此在理想情况下,他们每个人有 2 个工作单位。
现在,如果 2 名员工获得所有 12 个单位,则约束可能类似于:“对于每个员工,将他的努力与平均努力的差异平方,并对结果进行惩罚”。在上面提到的情况下,那将是
(6-2) squared + (6-2) squared + (0-2) squared + (0-2) squared + (0-2) squared + (0-2) squared
所以 16+16+4+4+4+4=48 个惩罚
在理想情况下,您有:
(2-2) squared + (2-2) squared + (2-2) squared + (2-2) squared + (2-2) squared + (2-2) squared
所以 0 个惩罚
如果你不考虑平方,你会:
(6-2) + (6-2) + (0-2) + (0-2) + (0-2) + (0-2)
所以 4+4-2-2-2-2 = 0 惩罚,这将完全违背约束的目标
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。