如何记录OptaPlanner求解历史?获取Top N最优解方法咨询
问题原因分析
你遇到的SolverEventListener仅捕获2条记录的核心原因是:OptaPlanner默认的BestSolutionRecaller仅在新解的得分严格优于当前已记录的最优解时,才会触发bestSolutionChanged事件。调试日志中的移动操作大多是求解过程中的试探性优化,并未产生比当前最优更优的解;或是10秒内早期就锁定了最优解,后续移动无法突破得分上限,因此事件触发次数极少。
实现Top N最优解的两种方案
方案一:基于SolverEventListener+自定义Top N列表
通过配置BestSolutionRecaller放宽触发条件,结合自定义监听器维护有序的Top N解集合:
- 修改Solver配置,允许记录与最优解得分接近的候选解(根据你的得分类型调整阈值):
<solver> <!-- 其他配置(终止策略、求解器算法等) --> <bestSolutionRecaller> <!-- 设置得分差值阈值,例如允许比最优解差100的解触发事件 --> <bestSolutionScoreDifferenceThreshold>100</bestSolutionScoreDifferenceThreshold> </bestSolutionRecaller> </solver>
- 自定义监听器实现Top N收集:
import org.optaplanner.core.api.solver.Solver; import org.optaplanner.core.api.solver.SolverEventListener; import org.optaplanner.core.api.solver.event.BestSolutionChangedEvent; import java.util.PriorityQueue; import java.util.List; import java.util.stream.Collectors; public class TopNSolutionListener<Solution_> implements SolverEventListener<Solution_> { private final int topN; private final PriorityQueue<Solution_> topSolutions; private final java.util.Comparator<Solution_> solutionComparator; public TopNSolutionListener(int topN, java.util.Comparator<Solution_> solutionComparator) { this.topN = topN; this.solutionComparator = solutionComparator; // 优先队列:头部为当前Top N中最差的解,方便溢出时移除 this.topSolutions = new PriorityQueue<>(topN, solutionComparator); } @Override public void bestSolutionChanged(BestSolutionChangedEvent<Solution_> event) { Solution_ newSolution = event.getNewBestSolution(); // 必须克隆解,避免OptaPlanner后续修改覆盖已记录的解 Solution_ clonedSolution = event.getSolver().getSolutionManager().cloneSolution(newSolution); if (topSolutions.size() < topN) { topSolutions.add(clonedSolution); } else { // 若新解比队列中最差的解更优,则替换 if (solutionComparator.compare(clonedSolution, topSolutions.peek()) > 0) { topSolutions.poll(); topSolutions.add(clonedSolution); } } } // 获取从最优到最差排序的Top N解 public List<Solution_> getTopNSolutions() { return topSolutions.stream() .sorted(solutionComparator.reversed()) .collect(Collectors.toList()); } }
- 使用监听器:
// 构建求解器 Solver<MySolution> solver = SolverFactory.create(solverConfig).buildSolver(); // 实例化监听器(以Top 10为例,按得分降序比较) TopNSolutionListener<MySolution> topNListener = new TopNSolutionListener<>(10, (sol1, sol2) -> sol1.getScore().compareTo(sol2.getScore())); solver.addEventListener(topNListener); // 启动求解 solver.solve(initialSolution); // 获取Top 10最优解 List<MySolution> top10Solutions = topNListener.getTopNSolutions();
方案二:基于StepListener收集所有候选解
如果你需要更全面的候选解(每个求解步骤的解都纳入筛选),可以使用StepListener监听每个步骤的结束事件,再筛选Top N:
import org.optaplanner.core.api.solver.Solver; import org.optaplanner.core.impl.solver.event.StepListenerAdapter; import org.optaplanner.core.impl.solver.scope.StepScope; import java.util.PriorityQueue; import java.util.List; import java.util.stream.Collectors; public class TopNStepListener<Solution_> extends StepListenerAdapter<Solution_> { private final int topN; private final PriorityQueue<Solution_> topSolutions; private final java.util.Comparator<Solution_> solutionComparator; private final Solver<Solution_> solver; public TopNStepListener(int topN, java.util.Comparator<Solution_> solutionComparator, Solver<Solution_> solver) { this.topN = topN; this.solutionComparator = solutionComparator; this.solver = solver; this.topSolutions = new PriorityQueue<>(topN, solutionComparator); } @Override public void stepEnded(StepScope<Solution_> stepScope) { Solution_ currentSolution = stepScope.getWorkingSolution(); Solution_ clonedSolution = solver.getSolutionManager().cloneSolution(currentSolution); if (topSolutions.size() < topN) { topSolutions.add(clonedSolution); } else { if (solutionComparator.compare(clonedSolution, topSolutions.peek()) > 0) { topSolutions.poll(); topSolutions.add(clonedSolution); } } } public List<Solution_> getTopNSolutions() { return topSolutions.stream() .sorted(solutionComparator.reversed()) .collect(Collectors.toList()); } }
使用方式:
Solver<MySolution> solver = SolverFactory.create(solverConfig).buildSolver(); TopNStepListener<MySolution> topNListener = new TopNStepListener<>(10, (sol1, sol2) -> sol1.getScore().compareTo(sol2.getScore()), solver); solver.addStepListener(topNListener); solver.solve(initialSolution); List<MySolution> top10Solutions = topNListener.getTopNSolutions();
关键注意点
- 必须克隆解:OptaPlanner在求解过程中会复用同一个
Solution实例进行修改,若不克隆,后续的优化操作会覆盖已记录的解。 - 得分比较:确保你的
Solution类能通过getScore()获取有效得分,且比较器逻辑符合你的优化目标(如最大化/最小化得分)。
内容的提问来源于stack exchange,提问作者Zhetao
相关产品推荐
相关产品推荐

