Optaplanner自定义阶段开发疑难:无法访问Indictment Map及自定义Phase配置不被识别的解决方法
我来帮你一步步拆解这些问题,逐个找到解决方案:
1. 无法访问IndictmentMap的问题
你碰到的IllegalStateException本质是约束匹配功能默认未开启——OptaPlanner默认关闭constraintMatchEnabled来节省性能,而IndictmentMap依赖这个功能收集约束违规信息。要开启它很简单:
- 如果用XML配置Solver:
<solver> <!-- 保留你原有的其他配置 --> <constraintMatchEnabled>true</constraintMatchEnabled> </solver> - 如果用Java代码配置:
SolverConfig solverConfig = new SolverConfig() // 其他配置项 .withConstraintMatchEnabled(true);
⚠️ 小提醒:开启这个功能会有轻微性能开销,但对于你的清理阶段这种一次性操作来说,完全在可接受范围内。开启后就能正常调用constraintStreamScoreDirector.getIndictmentMap()获取违规实体了。
2. 定位违反硬约束实体的替代方案
如果不想开启约束匹配功能,还有两种更轻量的方式可以找到违规站点:
方式一:手动校验实体约束
直接遍历所有站点,逐一检查硬约束是否满足:
// 假设你的Solution类中有获取所有站点的方法 for (Stop stop : scoreDirector.getWorkingSolution().getAllStops()) { // 这里替换成你实际的硬约束校验逻辑,比如车辆合法性、时间窗等 if (!isStopCompliantWithHardConstraints(stop)) { // 标记并修改实体时,必须通知ScoreDirector跟踪变化 scoreDirector.beforeVariableChanged(stop, "vehicle"); stop.setVehicle(dummyVehicle); scoreDirector.afterVariableChanged(stop, "vehicle"); } }
这种方式不需要额外依赖,缺点是需要你手动复现硬约束的校验逻辑。
方式二:用约束流收集违规实体
在你的约束定义中,除了计分逻辑,额外把违反硬约束的站点收集到一个全局集合里:
// 定义线程安全的集合存储违规站点 private final Set<Stop> invalidStops = Collections.synchronizedSet(new HashSet<>()); // 在约束流中添加收集逻辑 Constraint invalidStopConstraint = constraintFactory.from(Stop.class) .filter(stop -> /* 这里写硬约束违反的判断条件 */) .penalize("Invalid Stop", HardMediumSoftScore.ONE_HARD) .collectInto(invalidStops);
之后在清理阶段直接访问invalidStops集合,就能拿到所有需要处理的违规站点了。
3. 让PhaseFactory识别自定义阶段的问题
你遇到的IllegalArgumentException是因为自定义了CleanUpPhaseConfig,但OptaPlanner的PhaseFactory不知道如何处理这个未知的配置类型。最省心的解决方式是不用自定义Config类,直接用OptaPlanner自带的CustomPhaseConfig:
XML配置方式
<solver> <!-- 先保留你的构造启发式阶段配置 --> <constructionHeuristic>...</constructionHeuristic> <!-- 添加自定义清理阶段 --> <customPhase> <customPhaseCommandClass>org.acme.CleanUpPhase</customPhaseCommandClass> </customPhase> <!-- 再添加局部搜索阶段配置 --> <localSearch>...</localSearch> </solver>
Java代码配置方式
SolverConfig solverConfig = new SolverConfig() .withSolutionClass(Schedule.class) .withEntityClasses(Stop.class) // 添加构造启发式阶段 .addPhaseConfig(new ConstructionHeuristicPhaseConfig()) // 添加自定义清理阶段 .addPhaseConfig( new CustomPhaseConfig() .withCustomPhaseCommandClass(CleanUpPhase.class) ) // 添加局部搜索阶段 .addPhaseConfig(new LocalSearchPhaseConfig());
如果确实需要给自定义阶段传递额外参数,再考虑自定义Config并实现PhaseConfigurer接口,但绝大多数场景下,上面的方法足够满足需求。
额外建议:尝试让CH阶段直接生成可行解
虽然你的问题是过度约束,但可以试试给构造启发式阶段开启可行性优先模式,让它优先满足硬约束:
<constructionHeuristic> <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType> <feasibilityEnabled>true</feasibilityEnabled> </constructionHeuristic>
开启后CH会优先选择不违反硬约束的分配方式,如果问题确实过度约束,再用你的清理阶段兜底即可。
内容的提问来源于stack exchange,提问作者Magnus Våge

