如何在configSolver.xml中用Selection Filter覆盖默认规划阶段配置?
问题
基于现有configSolver.xml配置,我想给ChangeMove和SwapMove类添加选择过滤器,以此覆盖默认配置——据我所知,局部搜索阶段默认使用这两种移动的并集,构造启发式阶段默认使用每个规划值的ChangeMove笛卡尔积。
我想知道:是否必须先重现这两个阶段的默认配置,再应用选择过滤器进行覆盖?有没有更优的方法?我已经尝试了一套配置(目前运行正常,但不确定是否为最佳实践),同时参考官方文档实现了对应的过滤器,但使用过滤器后解决方案质量略低于默认配置。
我尝试的配置
<!-- 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>
实现的过滤器
ChangeMove过滤器
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()); } }
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.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()); } }
补充说明
上述方案运行正常,但不确定通过选择过滤器覆盖构造启发式和局部搜索阶段的默认配置是否为最佳实践,目前调度问题的解决方案质量略低于未使用过滤器的默认配置。
环境信息
- Timefold Solver 1.9.0 Enterprise Edition
- move-thread-count设为AUTO(本机为4)
- Java版本:21
- Spring Boot 3.2.3
内容的提问来源于stack exchange,提问作者Diallo Francis Patrick
相关产品推荐
相关产品推荐

