我有一个称为
@PlanningEntity
的 Participation
,其中 @PlanningVariable
enrolled
指示 Student
是否正在参加相关的 Appointment
:
@PlanningEntity
public class Participation {
private Student student;
private Appointment appointment;
@PlanningVariable
private Boolean enrolled = false;
}
在我的具体用例中,最佳解决方案通常是大多数(但不是全部)
Participation
都参加的解决方案。事实上,如果我将 PlanningVariable
初始化为 null
而不是 false
并从 FIRST_FIT
构建阶段开始,我会获得大约 3-4 倍的性能提升,以找到相同的解决方案(或在至少)。我想是因为大多数 enrolled
值设置为 true
比 LocalSearchPhase
期间可能更有效。
支持我的
PlanningSolution
的数据模型目前无法区分未参加的 Participation
(enrolled = false
) 和尚未做出的选择 (enrolled = null
)。因此,尽管这是一个可能的解决方案,但理想情况下,我希望通过使用假的 PlanningEntity
值初始化我的 enrolled
(预先将其设置为 null
)来防止更改基础数据。
我尝试过使用自定义 Forager 配置进行实验,例如使用
.withPickEarlyType(LocalSearchPickEarlyType.FIRST_BEST_SCORE_IMPROVING)
并实现自定义 SelectionFilter
,仅选择具有 enrolled==false
的实体,但这似乎没有太大区别。
那么,问题是:是否可以在当前解决方案已初始化的解决方案上进行类似
FIRST_FIT
的构造?我基本上想告诉 Timefold 对每个实体只查看一次,并贪婪地设置 enrolled=true
(如果这会带来更好的解决方案分数)。
编辑:另一种尝试是实现
CustomPhaseCommand
作为我的求解器的第一阶段。直观上这似乎是一种合乎逻辑的方法,但是我不知道如何检查更改对我的解决方案分数的影响。如果我实现 changeWorkingSolution
将所有 enrolled
值设置为 true
,则不会执行该阶段,因为将 every 值设置为 true 不会提高解决方案分数。有没有办法从CustomPhaseCommand
内部检查改进?
编辑2:请参阅下面我的“答案”
我通过
CustomPhaseCommand
找到了一个完全按照预期工作的解决方案。我已经执行了如下命令:
class InitialConstructionPhase implements CustomPhaseCommand<Timetable> {
@Override
public void changeWorkingSolution(ScoreDirector<Timetable> scoreDirector) {
InnerScoreDirector<Timetable, HardSoftScore> innerScoreDirector = (InnerScoreDirector<Timetable, HardSoftScore>) scoreDirector;
scoreDirector.getWorkingSolution().getParticipations().stream().filter(participation -> {
return !participation.getEnrolled();
}).forEach(participation -> {
HardSoftScore oldScore = innerScoreDirector.calculateScore();
setParticipationEnrollment(scoreDirector, participation, true);
HardSoftScore newScore = innerScoreDirector.calculateScore();
if(oldScore.compareTo(newScore) > 0) {
setParticipationEnrollment(scoreDirector, participation, false);
}
});
}
private void setParticipationEnrollment(ScoreDirector<Timetable> scoreDirector, Participation participation, boolean enrolled) {
scoreDirector.beforeVariableChanged(participation, "enrolled");
participation.setEnrolled(enrolled);
scoreDirector.afterVariableChanged(participation, "enrolled");
scoreDirector.triggerVariableListeners();
}
}
我在
https://github.com/TimefoldAI/timefold-solver/blob/7bb9cc2b67c51e88f1ef72bfa940b5df0de9bb91/core/core-impl/src/test/java/ai/timefold/solver/core/config中找到了
InnerScoreDirector
的案例/solver/EnvironmentModeTest.java#L276C13-L277C77 。这是实现此目标的推荐方法吗?像这样对InnerScoreDirector
进行强制转换感觉很奇怪,但我没有看到另一种方法来计算问题分数在CustomPhaseCommand期间。
值得注意的是,我看到 在 Optaplanner 7.29.0.Final 中,
calculateScore
方法可在 ScoreDirector
上使用,但我猜这已被删除?
我不完全确定为什么需要求解器来执行此操作。引用你的话:
那么,问题是:是否可以在当前解决方案已初始化的解决方案上进行类似 FIRST_FIT 的构造?我基本上想告诉 Timefold 对每个实体只查看一次,如果这会带来更好的解决方案分数,则贪婪地设置 enrolled=true 。
这对于本地搜索或约束求解器来说并不是真正的问题。这是一个简单的循环,它修改解决方案并计算每一步的分数。我建议您查看
SolutionManager#update(Solution)
,并在循环中使用它。
您将失去增量计算的所有性能优势。如果需要非常快地完成此操作,可以通过
SolutionManager
执行与 InnerScoreDirector
相同的操作。请注意,InnerScoreDirector
和相关 API 是私有的,不能保证稳定。