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

OptaPlanner中学生考试间隔约束失效问题求助

OptaPlanner考试间隔软约束失效问题修复

问题背景

需要实现同一学生的两场考试时间间隔越短,惩罚值越高的软约束:

  • 间隔1个时段:惩罚16
  • 间隔2个时段:惩罚8
  • 间隔3个时段:惩罚4
  • 间隔4个时段:惩罚2
  • 间隔5个时段:惩罚1
    当前代码运行后产生24645个软惩罚,但OptaPlanner完全没有尝试修复,核心问题出在约束逻辑的实现上。

原代码的关键错误

  1. 共享变量penalty的线程安全问题:ConstraintProvider是单例,成员变量penalty会被所有约束计算共享,导致惩罚值混乱,无法正确对应每个违规的考试对。
  2. 约束匹配逻辑错误:penalize方法中使用的penalty是最后一次filter中设置的值,而非当前匹配项对应的惩罚值,导致所有违规项的惩罚值统一为最后一次计算的结果。
  3. 低效的全量Join:直接Student.join(Exam).join(Exam)会产生大量无关匹配,既影响性能,也可能引入错误的约束匹配。

修复后的约束实现代码

public class ExamTableConstraintProvider implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[]{
                // Hard constraints
                twoExamForStudentConflict(constraintFactory),
                // Soft constraints
                spaceBetweenExams(constraintFactory)
        };
    }

    private Constraint twoExamForStudentConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(Exam.class)
                .join(Exam.class,
                        Joiners.equal(Exam::getTimeslot),
                        Joiners.lessThan(Exam::getID))
                .filter((exam1, exam2) -> {
                    List<Integer> commonStudents = new ArrayList<>(exam1.getSID());
                    commonStudents.retainAll(exam2.getSID());
                    return !commonStudents.isEmpty();
                })
                .penalize("Student conflict", HardSoftScore.ONE_HARD);
    }

    private Constraint spaceBetweenExams(ConstraintFactory constraintFactory) {
        // 先关联学生和其所有考试
        return constraintFactory.forEach(Student.class)
                .join(Exam.class, Joiners.equal(student -> student.getExamIds(), Exam::getID))
                // 再关联同一学生的另一场考试,用lessThan避免重复匹配(如exam1-exam2和exam2-exam1)
                .join(Exam.class,
                        Joiners.equal((student, exam1) -> student.getExamIds(), Exam::getID),
                        Joiners.lessThan(Exam::getID, (student, exam1) -> exam1.getID()))
                .filter((student, exam1, exam2) -> {
                    // 仅对间隔≤5的情况进行惩罚
                    int timeDifference = Math.abs(exam1.getTimeslot().getID() - exam2.getTimeslot().getID());
                    return timeDifference >= 1 && timeDifference <= 5;
                })
                // 动态计算当前匹配项的惩罚值,避免共享变量
                .penalize("Max time between exams",
                        HardSoftScore.ONE_SOFT,
                        (student, exam1, exam2) -> {
                            int timeDifference = Math.abs(exam1.getTimeslot().getID() - exam2.getTimeslot().getID());
                            return switch (timeDifference) {
                                case 1 -> 16;
                                case 2 -> 8;
                                case 3 -> 4;
                                case 4 -> 2;
                                case 5 -> 1;
                                default -> 0; // 前面filter已经过滤,这里不会触发
                            };
                        });
    }
}

关键改动说明

  1. 移除共享变量penalty:在penalize方法中使用lambda表达式,针对每个匹配的(student, exam1, exam2)动态计算惩罚值,确保每个违规项的惩罚值准确对应。
  2. 优化Join逻辑:
    • 用Joiners.equal(student -> student.getExamIds(), Exam::getID)精准关联学生和其参加的考试,避免全量匹配。
    • 用Joiners.lessThan(Exam::getID, (student, exam1) -> exam1.getID())确保每个考试对只被匹配一次(如exam1 ID < exam2 ID),避免重复惩罚。
  3. 精准过滤违规场景:仅对时间间隔1-5的考试对进行惩罚,减少不必要的约束计算。

额外验证建议

  • 运行Solver时,开启日志查看约束匹配情况:添加logging.level.org.optaplanner=DEBUG到配置文件,确认软约束的惩罚值是否正确计算。
  • 延长Solver运行时间(如改为Duration.ofMinutes(5)),观察软惩罚是否逐渐降低,确认Solver正在优化该约束。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 13:20:28