I have a ProblemFact AgentAvailability which reference a shadow planning entity Agent (Agent has no PlanningVariable, only an InverseRelationShadowVariable referencing the PlanningEntity Shift)
Here is my class definitions:
Shift.class
@PlanningEntity
public class Shift extends AbstractPersistable implements Comparable<Shift> {
@PlanningVariable(valueRangeProviderRefs = { "agentRange" }, nullable = true)
private Agent agent;
private Spot spot;
private LocalDateTime startDateTime;
private LocalDateTime endDateTime;
//Constructor, getters, setters and JPA annotations removed for clarity
Agent.class
@PlanningEntity
public class Agent implements Comparable<Agent> {
@PlanningId
private long registrationNumber;
private String username;
private boolean pro;
private SortedSet<Skill> skillSet = new TreeSet<>();
@InverseRelationShadowVariable(sourceVariableName = "agent")
private SortedSet<Shift> shiftSet = new TreeSet<>();
//Constructor, getters, setters and JPA annotations removed for clarity
AgentAvailability.class
public class AgentAvailability extends AbstractPersistable implements Comparable<AgentAvailability> {
private Agent agent;
private LocalDateTime startDateTime;
private LocalDateTime endDateTime;
//Constructor, getters, setters and JPA annotations removed for clarity
and here is my Solution definition:
Schedule.class
@PlanningSolution
public class Schedule extends AbstractPersistable {
// ...
@ProblemFactCollectionProperty
@ValueRangeProvider(id = "agentRange")
private List<Agent> agentList = new ArrayList<>();
@ProblemFactCollectionProperty
private List<AgentAvailability> agentAvailabilities = new ArrayList<>();
@PlanningEntityCollectionProperty
private List<Shift> shifts = new ArrayList<>();
// ...
//Constructor, getters, setters and JPA annotations removed for clarity
the inverseShadowVariable is working as expected: when an Agent is assigned to a Shift, the shiftSet from the assigned Agent is updated with this shift.
The problem is with the field agent from AgentAvailability.
From my understanding, when Optaplanner clones the solution, according to documentation, it reuses instances for ProblemFacts, and uses cloning for planning entities.
Since AgentAvailability is a Fact I assume it's reused (and in debugger view, this is the case, instance is the same between initial problem and bestSolution found)
Agent from Schedule are correctly cloned with their shiftSet to the solution too.
But the problem is that agent field in AgentAvailability is still the Agent instance from the original problem.
Therefore, the field shiftSet of agent in AgentAvailability instances is still empty.
It is the case at the end in the bestSolution found, but also during computing.
Therefore, I cannot use a constraint like this:
return constraintFactory.from(AgentAvailability.class)
.groupBy(AgentAvailability::getAgent, count())
.filter((agent, availabilities) -> availabilities - agent.getShiftSet().size() > 0)
.penalizeConfigurableLong(ShiftSchedulingConstraintConfiguration.PRO_HAS_SHIFT, (agent, availabilities) -> availabilities - agent.getShiftSet().size());
since agent.getShiftSet().size() always return 0 even when the agent has been assigned to shifts.
A possible Workaround would be to use a from(Agent.class) clause, and join with AgentAvailabilities and Shift to "rebuild" the shiftSet from the Agent Entity. but is there any way to "clone" the shiftSet from Agent entities in the Agent of AgentAvailability so I can use agent field from AgentAvailability with updated shiftSet?