OptaPlanner中难度比较器未生效问题排查求助
问题描述
我尝试为实体类Lecture添加难度比较器,实现最难调度的课程优先被调度,但该比较器似乎未对求解器产生影响。
我的配置
Lecture包含startTimeslot和room两个规划变量,均设置nullable = true- 未使用构造启发式算法,仅启用本地搜索
- 本地搜索配置:
<localSearch> <termination> <unimprovedSecondsSpentLimit>300</unimprovedSecondsSpentLimit> </termination> <changeMoveSelector> <filterClass>my.solver.PinnedTimeslotChangeMoveFilter</filterClass> </changeMoveSelector> <acceptor> <entityTabuSize>7</entityTabuSize> </acceptor> <forager> <acceptedCountLimit>1000</acceptedCountLimit> </forager> </localSearch>
- 曾尝试在
changeMoveSelector中添加如下配置:
<entitySelector> <cacheType>STEP</cacheType> <selectionOrder>SORTED</selectionOrder> <sorterManner>DECREASING_DIFFICULTY</sorterManner> </entitySelector>
- 尝试使用
difficultyWeightFactoryClass也无济于事 Lecture规划实体注解:
@PlanningEntity(difficultyComparatorClass = LectureDifficultyComparator.class) public class Lecture { ... }
LectureDifficultyComparator实现(仅用于验证功能):
public class LectureDifficultyComparator implements Comparator<Lecture> { Comparator<Lecture> comparator = Comparator.comparing( l -> l.getDisplayName().startsWith("HARD_") ? 1 : 0); @Override public int compare(Lecture o1, Lecture o2) { return comparator.compare(o1, o2); } }
观察求解过程发现,难排的课程很少被优先调度,甚至修改compare(o1, o2)为compare(o2, o1)也没有任何变化。
我的疑问
- 难度比较器仅为求解器的提示,是否我选择的算法会忽略它?
- 规划变量设置
nullable是否会导致难度比较器被忽略? - 有哪些调试方法?可通过哪些日志查看OptaPlanner的执行逻辑?
解答
关于难度比较器的生效范围
难度比较器并非仅作为提示,但它的生效场景和你选择的算法强相关:
- 构造启发式算法是核心生效场景:比如
FirstFitDecreasing、CheapestInsertion这类策略,会严格按照难度排序优先安排高难度实体。你当前仅启用本地搜索,完全跳过了构造启发式阶段——本地搜索基于已有初始解做局部优化,不会主动按难度优先级调度实体,自然触发不了难度比较器的核心逻辑。 - 本地搜索中的Move选择配置:你尝试在
changeMoveSelector里配置按难度排序的实体选择器,理论上应该生效,但要排查两个点:- 检查
PinnedTimeslotChangeMoveFilter的过滤规则:如果它过滤掉了大部分高难度实体,排序逻辑就无法体现。 - 确认排序方向:你的比较器中,
HARD_实体返回1,comparator.compare(o1, o2)会把HARD实体排在后面(1>0),修改为comparator.compare(o2, o1)才会让HARD实体优先。如果调整后仍无变化,可能是缓存机制(cacheType=STEP)导致排序未实时更新。
- 检查
规划变量nullable的影响
nullable = true不会直接忽略难度比较器。这个配置只是允许实体处于未分配状态,难度比较器的逻辑独立于变量分配状态。但要注意:跳过构造启发式后,初始解是随机分配的,本地搜索不会主动优先调整未分配的高难度实体——除非你在Move选择器里明确优先选择未分配实体,或者在分数函数中给未分配的高难度实体设置更高惩罚权重。
调试方法与日志查看
1. 验证比较器是否被调用
直接在LectureDifficultyComparator的compare方法中添加打印日志,确认求解器是否真的触发了比较逻辑:
@Override public int compare(Lecture o1, Lecture o2) { int result = comparator.compare(o1, o2); System.out.printf("对比实体:%s vs %s,结果:%d%n", o1.getDisplayName(), o2.getDisplayName(), result); return result; }
同时查看求解器启动日志,搜索difficultyComparatorClass,确认配置是否被正确识别。
2. 开启OptaPlanner调试日志
在日志配置(如logback.xml)中添加以下规则,查看执行细节:
<!-- 查看实体选择、Move生成过程 --> <logger name="org.optaplanner.core.impl.heuristic.selector" level="DEBUG"/> <!-- 查看本地搜索的接受/拒绝逻辑 --> <logger name="org.optaplanner.core.impl.localsearch" level="DEBUG"/> <!-- 查看分数计算细节 --> <logger name="org.optaplanner.core.impl.score" level="DEBUG"/>
这些日志会输出每次选中的实体、生成的Move、分数变化等信息,可直接判断高难度实体是否被优先处理。
3. 检查初始解状态
因为没有构造启发式,初始解是随机的。打印所有实体的分配状态和难度标识,确认高难度实体的初始分配情况,再观察本地搜索是否对其进行调整。
4. 排除配置干扰
暂时移除PinnedTimeslotChangeMoveFilter,测试难度排序是否生效;或者将cacheType改为JIT,避免缓存导致的排序不更新。
内容的提问来源于stack exchange,提问作者aioobe
相关产品推荐
相关产品推荐

