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

OptaPlanner生产任务规划中StartTime晚于EndTime问题求助

OptaPlanner规划任务中开始时间晚于结束时间的问题排查与解决

使用OptaPlanner 8.9.1.Final处理162个生产任务规划,要求任务在不同设备执行,时间范围为2023年12月1日至12月31日。运行后最优解中有7个任务的开始时间晚于结束时间,调整optaplanner.solver.termination.spent-limit和optaplanner.solver.termination.unimproved-spent-limit参数无效,以下是问题原因及解决办法:

原因分析

  • 规划变量设计缺陷:startDate和endDate作为独立的PlanningVariable,OptaPlanner会分别随机赋值,仅靠约束惩罚无法从根源上避免两者的逻辑冲突。当任务规模较大时,求解器容易陷入局部最优,无法修复所有违反硬约束的情况。
  • 约束引导性不足:仅设置硬惩罚约束,缺乏正向引导逻辑,求解器没有明确的方向去生成startDate < endDate的合法组合。
  • 初始解质量差:默认初始解可能大量存在startDate >= endDate的情况,增加了求解器的修复难度,即使调整终止时间,也可能无法覆盖全部修复路径。
  • 代码潜在错误:mismatchEquipmentSkill约束中,ProduceOrderTask::getEquipId与实体类的produceEquipment字段不匹配,可能导致该约束未生效;allWorkCenters值范围提供者未在ProducePlanningSolution中定义,produceEmployee变量无法获取合法值,分散求解器搜索注意力。

解决办法

1. 重构时间变量设计(推荐)

取消endDate的PlanningVariable注解,通过任务固定持续时间推导结束时间,从根源避免逻辑冲突:

@PlanningEntity
@Data
@EqualsAndHashCode(of = {"id"})
@TableName("produce_order_task")
public class ProduceOrderTask implements Serializable {
    // 其他字段保持不变
    @PlanningVariable(valueRangeProviderRefs = "taskTimeRange")
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime startDate;

    // 新增任务持续时间(固定值,作为问题事实)
    private Long durationHours;

    // 通过startDate和持续时间计算endDate,无需作为规划变量
    public LocalDateTime getEndDate() {
        return startDate == null ? null : startDate.plusHours(durationHours);
    }
}

2. 强化约束逻辑(若保留双变量设计)

  • 增加正向奖励约束,引导求解器生成合法时间组合:
protected Constraint validTaskTimeRange(ConstraintFactory constraintFactory) {
    return constraintFactory
            .from(ProduceOrderTask.class)
            .filter(task -> task.getStartDate() != null
                    && task.getEndDate() != null
                    && task.getStartDate().isBefore(task.getEndDate()))
            .reward("validTaskTime", HardSoftScore.ofSoft(10));
}
  • 提高硬惩罚权重,确保求解器优先修复该约束违反:将taskStartTimeNotBeforeEndTime的惩罚值从1000调整为10000。

3. 自定义初始解生成器

确保初始解中所有任务的startDate早于endDate,减少求解器修复压力:

public class ProduceTaskInitializer implements Initializer<ProducePlanningSolution> {
    @Override
    public void initialize(ProducePlanningSolution solution) {
        List<ProduceOrderTask> tasks = solution.getProduceOrderTasks();
        CountableValueRange<LocalDateTime> timeRange = solution.getTaskTimeRange();
        List<LocalDateTime> timePoints = timeRange.toList();
        for (ProduceOrderTask task : tasks) {
            // 随机选择合法的时间区间
            int startIdx = ThreadLocalRandom.current().nextInt(timePoints.size() - 1);
            LocalDateTime start = timePoints.get(startIdx);
            int endIdx = startIdx + 1 + ThreadLocalRandom.current().nextInt(timePoints.size() - startIdx - 1);
            LocalDateTime end = timePoints.get(endIdx);
            task.setStartDate(start);
            task.setEndDate(end);
            // 初始化设备等其他变量
            task.setProduceEquipment(solution.getProduceEquipments().get(ThreadLocalRandom.current().nextInt(solution.getProduceEquipments().size())));
        }
    }
}

在Solver配置中指定该初始解生成器。

4. 优化求解器配置

  • 调整局部搜索策略:将localSearch.type设置为TABU_SEARCH或LATE_ACCEPTANCE,增强跳出局部最优的能力。
  • 设置bestScoreLimit为0hard/*soft,让求解器直到找到无硬约束违反的解才停止(需确保问题存在可行解)。

代码修复建议

  • 修正mismatchEquipmentSkill约束的字段引用,将ProduceOrderTask::getEquipId改为ProduceOrderTask::getProduceEquipment,并调整关联逻辑:
.join(ProduceEquipment.class, equal(ProduceOrderTask::getProduceEquipment, ProduceEquipment::getId))
  • 在ProducePlanningSolution中补充allWorkCenters值范围提供者:
@ValueRangeProvider(id = "allWorkCenters")
@ProblemFactCollectionProperty
private List<ProduceEmployee> produceEmployees;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 05:59:59