我正在研究类似于optaplanner的护士安排示例的求解器(员工被分配到班次,员工是计划变量,转移计划实体),除了班次被分成1小时的间隔,员工可以按照每个班次工作天。
其中一个严格的限制是每个员工每月只能工作一定的时间。我目前使用以下规则对此进行建模,它的工作原理如下:
rule "At most 173h work per month per fulltime employee, assuming roster is only for 1 month"
when
$e: Employee(type == EmployeeType.FULLTIME)
$total : Number(intValue() > 10320)
from accumulate(
Shift(employee == $e,
$minutes : getTimeSlot().getMinutesInterval()),
sum($minutes))
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
现在,由于预计需要计划很多轮班(1000+),我想我可以通过跟踪每位员工的工作时间来加快处理速度。
为此,我给每个员工一个应该跟踪这些信息的对象(统计数据)。在Shift对象的setEmployee方法期间更新对象,如下所示:
public void setEmployee(Employee employee) {
if(this.employee != null){
this.employee.getStats().subtractWorkedMinutes(this.timeSlot);
}
this.employee = employee;
if(this.employee != null){
this.employee.getStats().addWorkedMinutes(this.timeSlot);
}
}
我还在护士计划示例中实现了EmployeeChangeMove和ShiftAssignmentSwapMove,以确保每次移动都调用setEmployee()方法,因此应该更改员工统计信息。现在我想在规则中使用这些递增计算的员工统计信息,如下所示:
rule "At most 173h work per month per fulltime employee, assuming roster is only for 1 month"
when
Employee(type == EmployeeType.FULLTIME,
getStats().getTotalMinutesWorked() > 10320)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
但是,我遇到的问题是这些规则在解决过程中从未触发过,并且解算器没有考虑这些规则来计算分数。只有在解算器完成后我才从解决方案中生成一个新的ScoreDirector来获取ConstraintMatchTotal对象,这是实际触发的规则并且确实显示为违反规则。
我究竟做错了什么?
在求解过程中跟踪变化变量以在约束规则中使用它们的最佳方法是什么?
当Shift.employee被OptaPlanner修改后,Drools会被告知Shift被修改(因此它可以进行delta增量分数计算)。 Drools没有被告知Employee也被修改了......
解决方案:使Employee.stats成为影子变量。
员工应该是一个影子@PlanningEntity
和它的统计字段应该是@CustomShadowVariable
,其源是setEmployee Shift.employee
,其监听器在Shift.setEmployee
中执行代码,以便setEmployee可以再次成为一个简单的setter。
除非正在进行扫描,否则不要忘记将Employee注册为求解器配置中的计划实体。