I’ve a business case to assign resources(employees) to some work requirements. Here are three domain classes to describe the problem:
public class Requirement { @PlanningId private Long id; private Long requirementId; private Integer requiredResourceSize; } public class Resource { @PlanningId private Long id; private String name; } /** The class defines the availability type of a certain day for a resource */ public class Availability { @PlanningId private Long id; private Resource resource; private LocalDate date; private AvailabilityType type; } public enum AvailabilityType { DESIRED, UNDESIRED, UNAVAILABLE; }
And a PlanningEntity
and PlanningSolution
class defines as follows:
@PlanningEntity public class RequirementAssignment { @PlanningId private Long id; private Requirement requirement; private Long requirementId; @PlanningVariable(valueRangeProviderRefs = "resourceRange", nullable = true) private Resource resource; public RequirementAssignment(long id, Requirement requirement, long requirementId) { this.id = id; this.requirement = requirement; this.requirementId = requirementId; } } @PlanningSolution public class ResourceSchedule { @ProblemFactCollectionProperty private List<Availability> availabilities; @ProblemFactCollectionProperty @ValueRangeProvider(id="resourceRange") private List<Resource> resources; @PlanningEntityCollectionProperty private List<RequirementAssignment> assignments; @PlanningScore private HardSoftBigDecimalScore score; private SolverStatus status; public ResourceSchedule(List<Availability> availabilities, List<Resource> resources, List<RequirementAssignment> assignments) { this.availabilities = availabilities; this.resources = resources; this.assignments = assignments; } }
The simplify the problem, I added only one Constraint
which scores -1 * (requirement's amount)
if no resources assigned to the requirement.
public class ResourceSchedulingConstraintProvider implements ConstraintProvider { @Override public Constraint[] defineConstraints(ConstraintFactory factory) { return new Constraint[] { unassignedRequirement(factory) }; } private Constraint unassignedRequirement(ConstraintFactory factory) { return factory.forEach(RequirementAssignment.class) .filter(assignment -> assignment.getResource() == null) .penalizeBigDecimal("Unassigned Requirement", HardSoftBigDecimalScore.ONE_HARD, assignment -> assignment.getRequirement().getAmount()); } }
The problem is that assignments
in all solved ResourceSchedule
hold null resource
field no matter input data, and the best score is always 0hard/0soft
which obviously contradicts with the defined Constraint
. Meanwhile, if I remove the nullable = true
in @PlanningVariable
, then everything seems work properly — there are minus score and RequirementAssignment
with Resource
information.
Advertisement
Answer
forEach(...)
only matches on planning entities with non-null variables. The solution is to use forEachIncludingNullVars(...)
instead, as stated in the documentation for nullable variables.