含多规划变量的Planning Entity无法切换约束场景的解决方案咨询
解决Timefold中规划变量联动切换的硬约束阻塞问题
问题分析
当前核心问题是:单独修改变量A会导致B处于非法值,触发硬约束从而被移动拒绝;而unionMoveSelector仅提供单独修改A或B的移动选项,无法实现两者的联动调整,因此无法完成合规的场景切换。
可行解决方案
1. 动态值范围提供器(推荐优先尝试)
修改B的值范围提供器,根据当前实体的A值动态返回合法的B选项,从根源上避免非法值被选中:
class C { @PlanningVariable private A a; @ValueRangeProvider private List<A> aScope; // A1 ... A15 @PlanningVariable(valueRangeProviderRefs = "bDynamicScope") private B b; // 动态B值范围,根据当前A的值返回合法选项 @ValueRangeProvider(id = "bDynamicScope") public List<B> getDynamicBScope(C entity) { if (entity.getA() == null) { // 初始状态返回所有B值,或根据需求调整 return Arrays.asList(B.B1, B.B2, B.B3, B.B4); } if (isInA1ToA10(entity.getA())) { return Arrays.asList(B.B1, B.B2, B.B3); } else if (isInA11ToA15(entity.getA())) { return Collections.singletonList(B.B4); } return Collections.emptyList(); } // 辅助判断方法,实现A的范围校验逻辑 private boolean isInA1ToA10(A a) { /* ... */ } private boolean isInA11ToA15(A a) { /* ... */ } }
这种方式下,当A被设置为A11-A15时,B的可选值仅为B4,移动时不会出现非法值,从根本上避免硬约束冲突。
2. 组合移动选择器
配置同时修改A和B的组合移动,确保每次修改A的同时,自动将B调整为符合约束的合法值。在@PlanningSolution类中添加如下配置:
@PlanningSolution public class YourSolution { // ... 其他代码 ... @MoveSelector( compositeMoveSelector = @CompositeMoveSelector( moveSelectors = { @MoveSelector(type = ChangeMove.class, entityClass = C.class, variableName = "a"), @MoveSelector(type = ChangeMove.class, entityClass = C.class, variableName = "b") }, selectionOrder = SelectionOrder.SEQUENTIAL, filterClass = ValidABCombinationMoveFilter.class ) ) public List<Move> getMoveSelectors() { return null; } }
实现过滤类,仅保留合法的A+B组合移动:
public class ValidABCombinationMoveFilter implements MoveFilter<YourSolution> { @Override public boolean accept(YourSolution solution, Move move) { // 校验组合移动的合法性 if (move instanceof CompositeMove) { List<ChangeMove> changeMoves = move.getMoves().stream() .filter(m -> m instanceof ChangeMove) .map(m -> (ChangeMove) m) .collect(Collectors.toList()); if (changeMoves.size() == 2) { C entity = (C) changeMoves.get(0).getEntity(); A newA = null; B newB = null; for (ChangeMove cm : changeMoves) { if (cm.getVariableName().equals("a")) newA = (A) cm.getToValue(); else if (cm.getVariableName().equals("b")) newB = (B) cm.getToValue(); } if (newA != null && newB != null) { if (isInA1ToA10(newA)) return isInB1ToB3(newB); else if (isInA11ToA15(newA)) return newB.equals(B.B4); } } } // 校验单独移动的合法性 if (move instanceof ChangeMove) { ChangeMove cm = (ChangeMove) move; C entity = (C) cm.getEntity(); if (cm.getVariableName().equals("a")) { A newA = (A) cm.getToValue(); if (isInA11ToA15(newA)) return entity.getB().equals(B.B4); else return isInB1ToB3(entity.getB()); } else if (cm.getVariableName().equals("b")) { B newB = (B) cm.getToValue(); if (isInA11ToA15(entity.getA())) return newB.equals(B.B4); else return isInB1ToB3(newB); } } return false; } // 辅助判断方法,实现A、B的范围校验逻辑 private boolean isInA1ToA10(A a) { /* ... */ } private boolean isInA11ToA15(A a) { /* ... */ } private boolean isInB1ToB3(B b) { /* ... */ } }
3. 自定义联动Move实现
编写同时修改A和B的自定义Move,确保每次修改A时自动同步B到合法值:
public class ChangeABMove implements Move<YourSolution> { private final C entity; private final A newA; private final B newB; private final A oldA; private final B oldB; public ChangeABMove(C entity, A newA, B newB) { this.entity = entity; this.newA = newA; this.newB = newB; this.oldA = entity.getA(); this.oldB = entity.getB(); } @Override public boolean isMoveDoable(YourSolution solution) { // 验证A+B组合的合法性 if (isInA1ToA10(newA)) return isInB1ToB3(newB); else if (isInA11ToA15(newA)) return newB.equals(B.B4); return false; } @Override public void doMove(YourSolution solution) { entity.setA(newA); entity.setB(newB); } @Override public void undoMove(YourSolution solution) { entity.setA(oldA); entity.setB(oldB); } @Override public Collection<? extends Object> getPlanningEntities() { return Collections.singleton(entity); } @Override public Collection<? extends Object> getPlanningValues() { return Arrays.asList(newA, newB); } // 辅助判断方法,实现A、B的范围校验逻辑 private boolean isInA1ToA10(A a) { /* ... */ } private boolean isInA11ToA15(A a) { /* ... */ } private boolean isInB1ToB3(B b) { /* ... */ } }
在@PlanningSolution中配置使用该自定义Move:
@PlanningSolution public class YourSolution { // ... @MoveSelector(type = CustomMove.class, customMoveClass = ChangeABMove.class) public List<Move> getCustomMoveSelectors() { return null; } }
关键提示
unionMoveSelector无效的原因是它仅合并了单独修改A或B的移动选项,并未实现两者的联动修改。上述方案中,动态值范围提供器是最简单高效的方式,推荐优先尝试。
内容的提问来源于stack exchange,提问作者user23726839
相关产品推荐
相关产品推荐

