You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

含多规划变量的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 18:45:04