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.
CodePudding user response:
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.