如何使用 configSolver.xml 中的选择过滤器覆盖默认配置的阶段?

问题描述 投票:0回答:1

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

编辑: 似乎上述解决方案效果很好,但我不知道这是否是使用选择过滤器覆盖构造启发式和本地搜索阶段的默认配置的最佳实践,因为调度问题解决方案的质量是比没有选择过滤器的默认配置稍差。

环境

    时间折叠求解器1.9.0企业版
  • 我将 move-thread-count 设置为 AUTO(我的机器为 4)。
  • Java版本是21
  • Spring Boot 3.2.3
java xml spring optaplanner timefold
1个回答
0
投票
如果我正确理解您的过滤器,那么您正在制作一个过滤器,以便课程仅获得正确长度的时间段。这是规划实体上的

ValueRangeProvider的一个很好的用例。 为此:

  1. 从 PlanningSolution 类中删除 Timeslot 的 ValueRangeProvider 注释。

  2. 在课程上添加一个用 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 中删除过滤器(因为每个课程都有自己独立的值范围)。

但是,正如文档中所述,通过限制每个课程的值范围,您可以有效地创建内置硬约束。这样做的好处是可以大大减少可能的解决方案的数量;然而,它也会剥夺优化算法暂时打破该约束以逃避局部最优的自由。

© www.soinside.com 2019 - 2024. All rights reserved.