关联规划实体的约束为何引发OptaPlanner分数损坏异常?
带取派任务的VRP约束导致Score Corruption问题排查与解决
问题背景
正在解决带取派任务的VRP问题。当前模型中:
Vehicle是首个@PlanningEntity,包含LoadJobs类型的@PlanningListVariableLoadJobs分为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后判断车辆
原因分析
- 增量评分机制失效:当前约束仅通过
forEach(LoadJob::class.java)声明单个LoadJob为依赖实体,但判断逻辑实际依赖配对的另一个LoadJob的vehicle属性。当配对任务的车辆分配变化时,OptaPlanner的增量评分无法感知到关联变化,不会重新计算该约束得分,导致工作分数与从头计算的分数不一致。 - 重复惩罚:同一
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
相关产品推荐
相关产品推荐

