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

关联规划实体的约束为何引发OptaPlanner分数损坏异常?

带取派任务的VRP约束导致Score Corruption问题排查与解决

问题背景

正在解决带取派任务的VRP问题。当前模型中:

  • Vehicle是首个@PlanningEntity,包含LoadJobs类型的@PlanningListVariable
  • LoadJobs分为PICKUP(取货)和DROPOFF(送货)两种类型
  • 每个LoadJob关联对应的Load,Load类同时关联其对应的取货和送货LoadJob

现有约束代码

为确保同一Load的取货和送货任务分配给同一车辆,编写的约束如下:

fun pickupAndDropoffOnSameVehicle(constraintFactory: ConstraintFactory): Constraint {
    return constraintFactory
        .forEach(LoadJob::class.java)
        .filter { it.load.pickup.vehicle != it.load.dropoff.vehicle }
        .penalizeConfigurable()
        .asConstraint(PICKUP_AND_DROPOFF_ON_SAME_VEHICLE)
}

异常信息

在FULL_ASSERT模式下运行时,抛出如下异常:

Caused by: java.lang.IllegalStateException: Score corruption (100hard): the workingScore (-19init/-100hard/0medium/-11670soft) is not the uncorruptedScore (-19init/-200hard/0medium/-11670soft) after completedAction (LoadJob(id=DROPOFF-loDKrYTAqF5kIfjFM6n4) {null -> Vehicle(idx=0)[0]}):
Score corruption analysis:
  The corrupted scoreDirector has no ConstraintMatch(s) which are in excess.
  The corrupted scoreDirector has 1 ConstraintMatch(s) which are missing:
    com.cargonexx.vehiclerouting.solver.constraint/pickupAndDropoffOnSameVehicle/[LoadJob(id=PICKUP-loDKrYTAqF5kIfjFM6n4)]=-100hard/0medium/0soft
  Maybe there is a bug in the score constraints of those ConstraintMatch(s).
  Maybe a score constraint doesn't select all the entities it depends on, but finds some through a reference in a selected entity. This corrupts incremental score calculation, because the constraint is not re-evaluated if such a non-selected entity changes.
Shadow variable corruption in the corrupted scoreDirector:
  None
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.assertScoreFromScratch(AbstractScoreDirector.java:637)
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.assertWorkingScoreFromScratch(AbstractScoreDirector.java:613)
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.doAndProcessMove(AbstractScoreDirector.java:204)
    at org.optaplanner.core.impl.heuristic.thread.MoveThreadRunner.run(MoveThreadRunner.java:131)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:577)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at java.base/java.lang.Thread.run(Thread.java:1589)

已尝试的无效方案

  • 仅筛选PICKUP任务再判断车辆是否相同
  • 关联两个LoadJob并筛选同一Load后判断车辆

原因分析

  1. 增量评分机制失效:当前约束仅通过forEach(LoadJob::class.java)声明单个LoadJob为依赖实体,但判断逻辑实际依赖配对的另一个LoadJob的vehicle属性。当配对任务的车辆分配变化时,OptaPlanner的增量评分无法感知到关联变化,不会重新计算该约束得分,导致工作分数与从头计算的分数不一致。
  2. 重复惩罚:同一Load的取货和送货任务会各自触发一次惩罚,导致重复扣分,这与异常中显示的100hard分数差直接对应。

解决方案

方案1:基于Load类构建约束(推荐)

直接以Load为约束主体,每个Load仅触发一次惩罚,同时OptaPlanner会监控配对任务的vehicle变化,确保增量计算准确:

fun pickupAndDropoffOnSameVehicle(constraintFactory: ConstraintFactory): Constraint {
    return constraintFactory
        .forEach(Load::class.java)
        .filter { it.pickup.vehicle != it.dropoff.vehicle }
        .penalizeConfigurable()
        .asConstraint(PICKUP_AND_DROPOFF_ON_SAME_VEHICLE)
}

方案2:关联配对LoadJob并去重

若必须基于LoadJob构建约束,通过join关联配对任务并过滤重复处理逻辑:

fun pickupAndDropoffOnSameVehicle(constraintFactory: ConstraintFactory): Constraint {
    return constraintFactory
        .forEach(LoadJob::class.java)
        .filter { it.type == LoadJobType.PICKUP } // 仅处理取货任务,避免重复
        .join(LoadJob::class.java,
              Joiners.equal { it.load }, // 关联同一Load的任务
              Joiners.filtering { pickup, dropoff -> dropoff.type == LoadJobType.DROPOFF })
        .filter { pickup, dropoff -> pickup.vehicle != dropoff.vehicle }
        .penalizeConfigurable()
        .asConstraint(PICKUP_AND_DROPOFF_ON_SAME_VEHICLE)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 11:31:12