简体   繁体   中英

Optaplanner: Drools rule not firing during solving, only after

I am working on a solver similar to the nurse scheduling example from optaplanner (Employees are assigned to shifts, employees are the planning variable, shifts the planning entity), except shifts are split into 1 hour intervals and an employee can work on multiple shifts per day.

One of the hard constraints is that each employee can only work a set amount of hours per month. I currently use the following rule to model this and it works:

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

Now, since it is expected that there are a lot of shifts that need to be planned (1000+) I figured I could speed up the processing by keeping track of hours worked per employee.

To that end I give each employee an object (stats) that should track this information. The object is updated during the Shift object's setEmployee Method like so:

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);
    }
}

I also implemented the EmployeeChangeMove and ShiftAssignmentSwapMove like in the nurse scheduling example, to make sure that each move calls the setEmployee() method and should therefore change the employee statistics. Now I wanted to use these incrementally calculated employee statistics in the rules, like so:

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

However, I encounter the problem that these rules are never fired during solving and aren't taking into account by the solver to calculate the scores. Only after the solver is finished and I generate a new ScoreDirector from the solution to get the ConstraintMatchTotal Object is the rule actually fired and does show up as a violated rule.

What am I doing wrong?

What would be the best way to keep track of changing variables during solving to make use of them in the constraint rules?

When Shift.employee gets modified by OptaPlanner, Drools get's told that Shift is modified (so it can do delta incremental score calculation). Drools doesn't get told that Employee is also modified...

Solution: make that Employee.stats a shadow variable.

Employee should be a shadow @PlanningEntity and it's stats field should a @CustomShadowVariable with its source being the setEmployee Shift.employee and its listener doing the code in Shift.setEmployee so that setEmployee can become a simple setter again.

Don't forget to register Employee as a planning entity in the solver configuration unless it's doing scanning.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM