Search code examples
optaplannertimefold

TimeFold allowsUnassigned is not assigning resources even with 0 constraints


We are using TimeFold for employee resource scheduling, I have set allowsUnassigned=true , however I observed that resource allocation is not happening even when constraints are not broken.

@PlanningVariable(allowsUnassigned = true)
private EmployeeShiftDetailsDto employeeShiftDetailsDto;

What I have tried is to make sure that

  1. There are no Constraints broken , below is the scoreExplain

    (0hard/0soft): Constraint matches:

         0: constraint (Contract Date) has 0 matches:
         0: constraint (Daywise score filter) has 0 matches:
         0: constraint (Prefer Permanent Employee) has 0 matches:
         0: constraint (Weekly score filter) has 0 matches:
     Indictments:
    
  2. Increase the number of employees to confirm if assignment happens

  3. Increased the timefold.solver.termination.spent-limit from 2m to 5m

Can i get some help to understand why resource allocation is not happening ?

PS : When I dont set allowsUnassigned flag, resource allocation is successful, fyi.


Solution

  • If allowsUnassigned=false, the solver has to assign everything, because the -init score will be negative until it does so. This also answers your question for allowsUnassigned=true. In this situation, if there is no constraint which penalizes unassigned entities, the solver will never do it because there is no motivation for it to do so.

    My guess is that you're struggling with forEach. This call will only ever match entities which are already assigned. Consider this constraint:

     forEach(Task.class)
         .filter(task -> task.employee == null)
         .penalize(SimpleScore.ONE)
         .asConstraint("Penalize unassigned tasks")
    

    This constraint is useless with allowsUnassigned=true. By default, nothing is assigned. forEach will only match things which are assigned. Therefore, there will never be any penalty, and therefore the solver will never actually attempt to assign anything. What you are looking for is forEachIncludingUnassigned():

     forEachIncludingUnassigned(Task.class)
         .filter(task -> task.employee == null)
         .penalize(SimpleScore.ONE)
         .asConstraint("Penalize unassigned tasks")
    

    This will match all tasks which are unassigned, filter out those that are assigned, and penalize every single unassigned task. And this will, in turn, incentivize the solver to make sure there is as few unassigned tasks as possible.