多规划变量场景下OptaPlanner SelectionFilter适配性及移动过滤方案咨询
多规划变量场景下OptaPlanner SelectionFilter适配性及移动过滤方案咨询
嗨,你的这个场景其实是OptaPlanner里非常典型的多规划变量依赖过滤问题,我来一步步给你拆解解答:
一、SelectionFilter能不能适配你的需求?
答案是肯定的,但需要灵活利用规划实体的上下文。虽然SelectionFilter的参数是Solution和候选的规划变量值,但你完全可以在Filter中获取当前的规划实体(B),进而拿到另一个变量的当前状态,以此做过滤:
- 比如要过滤
planningDate的候选值时,在Filter里先拿到当前实体的C字段值,再根据C的字段筛选允许的日期集合; - 同理,过滤
C的候选值时,拿到当前实体的planningDate,再筛选符合该日期的C列表。
举个贴合你类结构的伪代码示例:
// 过滤planningDate的SelectionFilter实现 public class PlanningDateFilter implements SelectionFilter<A, LocalDate> { @Override public boolean accept(A solution, B entity, LocalDate candidateDate) { // 获取当前实体已赋值的C C currentC = entity.getC(); if (currentC == null) { return true; // 未赋值C时允许所有日期候选 } // 根据C的字段筛选合法日期 return currentC.getAllowedFixedDates().contains(candidateDate); } }
不过你担心的那个问题确实存在:如果C1和C2对应的日期集合完全无交集,仅用单变量的SelectionFilter会导致无法从C1切换到C2——因为切换C的单变量移动会要求当前的planningDate必须在C2的允许日期集里,而当前日期是C1的合法日期(不在C2的集合里),所以这个移动会被直接过滤掉。
二、更推荐的方案:Filtered Move Selection + 组合移动
针对这个痛点,Filtered Move Selection是更合适的选择,它比SelectionFilter更灵活,因为它是对整个移动操作(而非单个候选值)做合法性检查:
- 对于单变量移动:比如修改C时,检查移动后新的C对应的日期集是否包含当前的planningDate;如果不包含,就过滤掉这个单变量移动;
- 对于组合移动:允许同时修改C和planningDate的移动(比如ChangeMove同时改变两个变量),只要修改后的C和planningDate互相匹配,就允许这个移动——这样就能直接从「C1+合法日期D1」切换到「C2+合法日期D2」,完美解决无交集时无法切换的问题。
具体配置思路
在Solver配置中,你可以这么做:
- 启用组合移动:配置同时修改C和planningDate的Composite Move,确保求解器能生成合法的跨变量组合操作;
- 配置MoveFilter:编写一个过滤规则,检查移动后的实体状态是否满足「C和planningDate互相匹配」的要求,只要满足就允许该移动。
给你一个MoveFilter的伪代码参考:
public class ValidEntityMoveFilter implements MoveFilter<A> { @Override public boolean accept(A solution, Move move) { // 从移动操作中获取目标规划实体 B targetEntity = (B) move.getPlanningEntities().get(0); // 模拟提取移动后的变量值(实际可通过Move的API获取修改后的状态) LocalDate newPlanningDate = extractNewPlanningDate(move); C newC = extractNewC(move); // 核心匹配检查:双向验证合法性 if (newC != null && newPlanningDate != null) { return newC.getAllowedDates().contains(newPlanningDate) && newPlanningDate.getAllowedCList().contains(newC); } // 未完全赋值时的宽松规则(可根据业务需求调整) return true; } // 辅助方法:从移动中提取新的planningDate值 private LocalDate extractNewPlanningDate(Move move) { // 实现根据Move类型提取新值的逻辑 return ...; } private C extractNewC(Move move) { // 实现根据Move类型提取新C值的逻辑 return ...; } }
三、其他补充建议
- 如果你希望更动态的候选值范围,也可以考虑动态ValueRangeProvider:比如在B类中,给
planningDate的ValueRangeProvider加上对C的依赖,但要注意OptaPlanner的动态范围缓存机制,确保范围能随C的变化实时更新; - 避免过度过滤:不要把所有“过渡性”的移动都过滤掉,比如可以允许临时的不匹配但通过分数惩罚来约束,这样求解器能更灵活地探索解空间,避免陷入局部最优。
内容来源于stack exchange
相关产品推荐
相关产品推荐

