configSolver.xml中的以下配置开始。
如果我想通过为 ChangeMove 和 SwapMove 类添加过滤器选择类来覆盖此配置(AFAIK,在本地搜索阶段默认使用这 2 个移动的并集,并且每个计划值的 ChangeMove 的笛卡尔积为在这个 configSolver.xml 中的构造启发阶段使用),我应该如何进行?我是否应该重现这两个阶段的默认配置,然后使用应用的选择过滤器覆盖它,或者是否有更好的方法?我尝试执行以下操作(目前看来它工作正常,但我不知道这是否是最佳实践):
<!-- Construction Heuristics generic config -->
<constructionHeuristic>
<constructionHeuristicType>ALLOCATE_ENTITY_FROM_QUEUE</constructionHeuristicType>
<entitySorterManner>DECREASING_DIFFICULTY_IF_AVAILABLE</entitySorterManner>
<valueSorterManner>DECREASING_STRENGTH_IF_AVAILABLE</valueSorterManner>
<cartesianProductMoveSelector>
<changeMoveSelector>
<!-- Apply the filter only for Timeslot planning value -->
<filterClass>com.patrick.timetableappbackend.utils.LessonChangeMoveFilter</filterClass>
<valueSelector variableName="timeslot"/>
</changeMoveSelector>
<changeMoveSelector>
<valueSelector variableName="room"/>
</changeMoveSelector>
</cartesianProductMoveSelector>
</constructionHeuristic>
<!-- Late_Acceptance configuration-->
<localSearch>
<!-- Termination configuration -->
<unionMoveSelector>
<changeMoveSelector>
<filterClass>com.patrick.timetableappbackend.utils.LessonChangeMoveFilter</filterClass>
</changeMoveSelector>
<swapMoveSelector>
<filterClass>com.patrick.timetableappbackend.utils.LessonSwapMoveFilter</filterClass>
</swapMoveSelector>
</unionMoveSelector>
<!-- Acceptor and forager -->
</localSearch>
<!-- Tabu_Search configuration-->
<localSearch>
<unionMoveSelector>
<changeMoveSelector>
<filterClass>com.patrick.timetableappbackend.utils.LessonChangeMoveFilter</filterClass>
</changeMoveSelector>
<swapMoveSelector>
<filterClass>com.patrick.timetableappbackend.utils.LessonSwapMoveFilter</filterClass>
</swapMoveSelector>
</unionMoveSelector>
<!-- Acceptor and forager -->
</localSearch>
请注意,我已经以与文档中的examples类似的方式分别实现了过滤器(针对ChangeMove和SwapMove)。
更改移动:
package com.patrick.timetableappbackend.utils;
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove;
import com.patrick.timetableappbackend.model.Lesson;
import com.patrick.timetableappbackend.model.Timeslot;
import com.patrick.timetableappbackend.model.Timetable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
public class LessonChangeMoveFilter implements SelectionFilter<Timetable, ChangeMove> {
private static final Logger LOGGER = LoggerFactory.getLogger(LessonChangeMoveFilter.class);
@Override
public boolean accept(ScoreDirector<Timetable> scoreDirector, ChangeMove changeMove) {
Lesson lesson = (Lesson) changeMove.getEntity();
Object planningValue = changeMove.getToPlanningValue();
// Ensure the value is of type Timeslot
if (!(planningValue instanceof Timeslot)) {
return true; // Accept the move if the target value is not a Timeslot
}
Timeslot toTimeslot = (Timeslot) planningValue;
return isMatching(lesson, toTimeslot);
}
private int calculateTimeslotDuration(Timeslot timeslot) {
Duration duration = Duration.between(timeslot.getStartTime(), timeslot.getEndTime());
return (int) duration.abs().toHours();
}
private boolean isMatching(Lesson lesson, Timeslot timeslot) {
int timeslotDuration = calculateTimeslotDuration(timeslot);
return (timeslotDuration == lesson.getDuration());
}
}
交换移动:
package com.patrick.timetableappbackend.utils;
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SwapMove;
import com.patrick.timetableappbackend.model.Lesson;
import com.patrick.timetableappbackend.model.Timeslot;
import com.patrick.timetableappbackend.model.Timetable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
public class LessonSwapMoveFilter implements SelectionFilter<Timetable, SwapMove> {
private static final Logger LOGGER = LoggerFactory.getLogger(LessonSwapMoveFilter.class);
@Override
public boolean accept(ScoreDirector<Timetable> scoreDirector, SwapMove swapMove) {
Lesson leftLesson = (Lesson) swapMove.getLeftEntity();
Lesson rightLesson = (Lesson) swapMove.getRightEntity();
return isMatching(leftLesson, rightLesson);
}
private int calculateTimeslotDuration(Timeslot timeslot) {
Duration duration = Duration.between(timeslot.getStartTime(), timeslot.getEndTime());
return (int) duration.abs().toHours();
}
private boolean isMatching(Lesson lessonA, Lesson lessonB) {
int timeslotDurationA = calculateTimeslotDuration(lessonA.getTimeslot());
int timeslotDurationB = calculateTimeslotDuration(lessonB.getTimeslot());
return (timeslotDurationA == lessonA.getDuration()) && (timeslotDurationB == lessonB.getDuration());
}
}
编辑: 似乎上述解决方案效果很好,但我不知道这是否是使用选择过滤器覆盖构造启发式和本地搜索阶段的默认配置的最佳实践,因为调度问题解决方案的质量是比没有选择过滤器的默认配置稍差。
环境
ValueRangeProvider的一个很好的用例。 为此:
@PlanningEntity
public class Lesson {
Timetable timetable; // Used to access possible timeslots
// ...
@ValueRangeProvider
public List<Timeslot> getPossibleTimeslots() {
return timetable.getTimeslots().stream().filter(this::matchesTimeslot).toList();
}
private boolean matchesTimeslot(Timeslot timeslot) {
var timeslotDuration = Duration.between(timeslot.getStartTime(), timeslot.getEndTime());
return (timeslotDuration.abs().toHours() == duration);
}
}
通过这些更改,可以从 SolverConfig 中删除过滤器(因为每个课程都有自己独立的值范围)。但是,正如文档中所述,通过限制每个课程的值范围,您可以有效地创建内置硬约束。这样做的好处是可以大大减少可能的解决方案的数量;然而,它也会剥夺优化算法暂时打破该约束以逃避局部最优的自由。