Skip to content
Advertisement

Nullable PlanningVariable causes all null solution

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.

Advertisement