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

OptaPlanner Constraint Streams如何动态定义约束为硬或软约束

OptaPlanner Constraint Streams 动态配置约束权重类型实现方案

场景说明

在使用OptaPlanner开发员工排班系统时,需要将原有DRL编写的排班规则重构为Constraint Streams实现,要求支持用户自定义单条规则的约束类型(硬约束/软约束)。
原有DRL实现可在规则then代码段访问Roster规划实体,动态判断当前规则需要触发硬扣分还是软扣分,但Constraint Streams的penalize方法默认要求传入固定的权重常量,原有写法无法直接复用,现有待改造代码如下:

Constraint holiday(ConstraintFactory constraintFactory) {
    return constraintFactory.forEach(Shift.class)
        .join(
          Absence.class,
          Joiners.equal(Shift::getEmployee, Absence::getEmployee),
          Joiners.greaterThanOrEqual(Shift::getDate, Absence::getStartDate),
          Joiners.lessThanOrEqual(Shift::getDate, Absence::getEndDate)
        ).penalize(
            "holiday",
            // 此处需要动态判断使用ONE_HARD还是ONE_SOFT
            HardMediumSoftLongScore.ONE_HARD,
            (shift,absence) -> {
              Roster roster = shift.getRoster();
              return roster.getRules().get("holiday").getPenaltyValue();
            }
          );
  }

可行实现方案

Constraint Streams的单条约束权重层级必须在约束构建阶段固定,不支持在事实匹配阶段动态切换硬/软权重层级,可根据业务场景选择以下两种实现方式:

  • 方案一:约束构建前提前加载配置(推荐,性能最优)
    Roster作为@PlanningSolution标注的规划解决方案对象,其携带的规则配置可以在构建Solver实例前提前读取,直接在约束定义阶段传入对应类型的固定权重,不需要在事实匹配阶段做动态判断。
    实现时可给ConstraintProvider增加带配置参数的构造函数,初始化时传入当前求解场景的规则配置:

    public class RosterConstraintProvider implements ConstraintProvider {
        // 持有当前场景的规则配置
        private final Map<String, RuleProperty> ruleConfigMap;
    
        // 构建ConstraintProvider时提前传入配置
        public RosterConstraintProvider(Map<String, RuleProperty> ruleConfigMap) {
            this.ruleConfigMap = ruleConfigMap;
        }
    
        @Override
        public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
            return new Constraint[] { holiday(constraintFactory) };
        }
    
        private Constraint holiday(ConstraintFactory constraintFactory) {
            RuleProperty holidayRule = ruleConfigMap.get("holiday");
            // 构建约束时直接确定权重层级
            HardMediumSoftLongScore baseWeight = holidayRule.isHard() ?
                    HardMediumSoftLongScore.ONE_HARD : HardMediumSoftLongScore.ONE_SOFT;
    
            return constraintFactory.forEach(Shift.class)
                    .join(
                            Absence.class,
                            Joiners.equal(Shift::getEmployee, Absence::getEmployee),
                            Joiners.greaterThanOrEqual(Shift::getDate, Absence::getStartDate),
                            Joiners.lessThanOrEqual(Shift::getDate, Absence::getEndDate)
                    ).penalizeLong(
                            "holiday",
                            baseWeight,
                            (shift, absence) -> holidayRule.getPenaltyValue()
                    );
        }
    }
    

    该方案没有额外的运行时判断开销,分数计算性能最好,是官方推荐的实现方式。如果不同排班场景的规则配置存在差异,每个场景求解前传入对应配置重新构建Solver实例即可。

  • 方案二:拆分双约束分支(适配无法提前读取配置的场景)
    如果受架构限制无法在约束构建阶段拿到规则配置,必须从匹配到的Shift关联的Roster对象上读取配置,可将单条业务规则拆分为硬约束、软约束两个独立的约束分支,匹配时先通过filter过滤出对应配置类型的事实,再分别执行对应层级的扣分:

    private Constraint[] holiday(ConstraintFactory constraintFactory) {
        // 抽离公共匹配逻辑,避免重复编写关联逻辑
        BiConstraintStream<Shift, Absence> baseMatchStream = constraintFactory.forEach(Shift.class)
                .join(
                        Absence.class,
                        Joiners.equal(Shift::getEmployee, Absence::getEmployee),
                        Joiners.greaterThanOrEqual(Shift::getDate, Absence::getStartDate),
                        Joiners.lessThanOrEqual(Shift::getDate, Absence::getEndDate)
                );
    
        // 硬约束分支:仅当规则配置为硬约束时扣分
        Constraint hardHoliday = baseMatchStream
                .filter((shift, absence) -> shift.getRoster().getRules().get("holiday").isHard())
                .penalizeLong("holiday",
                        HardMediumSoftLongScore.ONE_HARD,
                        (shift, absence) -> shift.getRoster().getRules().get("holiday").getPenaltyValue());
    
        // 软约束分支:仅当规则配置为软约束时扣分
        Constraint softHoliday = baseMatchStream
                .filter((shift, absence) -> !shift.getRoster().getRules().get("holiday").isHard())
                .penalizeLong("holiday",
                        HardMediumSoftLongScore.ONE_SOFT,
                        (shift, absence) -> shift.getRoster().getRules().get("holiday").getPenaltyValue());
    
        return new Constraint[]{hardHoliday, softHoliday};
    }
    

    注意:该方案会产生两条独立的约束匹配链路,运行时性能弱于方案一,仅作为兼容场景使用。绝对不要尝试在penalize的评分lambda中直接返回包含不同层级的Score对象,单条约束的权重层级必须和传入的基础权重保持一致,否则会出现评分计算错误。

内容的提问来源于stack exchange,提问作者Mischa Stone

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 05:06:04