ProblemFact 中具有 InverseRelationShadowVariable 的 PlanningEntity 字段未更新 - Optaplanner

PlanningEntity field with an InverseRelationShadowVariable in a ProblemFact is not updated - Optaplanner

我有一个 ProblemFact AgentAvailability 引用影子计划实体 AgentAgent 没有 PlanningVariable,只有一个 InverseRelationShadowVariable 引用 PlanningEntity Shift)

这是我的 class 定义:

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

这是我的解决方案定义:

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

inverseShadowVariable 按预期工作:当 Agent 分配给 Shift 时,shiftSet 来自分配的 Agent 随此班次更新。

问题出在 AgentAvailability 中的字段 agent

根据我的理解,当 Optaplanner 克隆解决方案时,根据 documentation,它会重用 ProblemFacts 的实例,并使用克隆来规划实体。

因为 AgentAvailability 是一个事实,我假设它被重用(在调试器视图中,情况就是这样,初始问题和找到的最佳解决方案之间的实例是相同的)

Agent 来自 ScheduleshiftSet 也被正确克隆到解决方案中。

但问题是 AgentAvailability 中的 agent 字段仍然是原始问题中的 Agent 实例。

因此,AgentAvailability 个实例中 agent 的字段 shiftSet 仍然是空的。

找到bestSolution最后是这样,计算的时候也是这样。

因此,我不能像这样使用 约束

 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());

因为 agent.getShiftSet().size() 始终 return 0,即使代理已分配班次。

一种可能的解决方法是使用 from(Agent.class) 子句,并与 AgentAvailabilities 和 Shift 结合以从 Agent 实体“重建”shiftSet。但是有什么方法可以从 AgentAvailabilityAgent 中的 Agent 实体中“克隆” shiftSet 这样我就可以使用来自 AgentAvailability 的代理字段和更新的 shiftSet

这是您的模型存在的问题。您不能期望 AgentAvailability 既被克隆又不被克隆。如果 AgentAvailability 是一个问题事实,它不会被克隆,因此 agent 的值不会被改变。

(旁注:也许我们应该检测一个实体是否从规划事实中引用,并快速失败?我看不出这种情况除了麻烦之外还会导致什么。)

这个问题有几种解决方法。我建议您将 Agent 设为问题事实并创建一个新实体 AgentAssignment,将 1 对 1 映射到 Agent。在此模型中,您可以在其他事实中自由引用 Agent 个事实实例。

这样约束就变得相对容易写了。

from(AgentAssignment.class)
    .join(AgentAvailability.class,
          Joiners.equal(
              AgentAssignment::getAgent,
              AgentAvailability::getAgent))
    .groupBy(
         (agentAssignment, agentAvailability) -> agentAssignment, 
         countBi())
    .filter((agentAssignment, availabilities) -> availabilities - agentAssignment.getShiftSet().size() > 0)
    ...

作为替代解决方案,在 AgentAvailability 上添加 @DeepPlanningClone 注释:

@DeepPlanningClone
public class AgentAvailability ... { ... }

不过 Lukas 的回答更好。