如何在双实体模型中评估Constraint Stream总分并实现资源上限管控?
解决方案
方法1:合并约束流直接计算总分约束
既然目标是控制两个约束流的总和,最直接的方式是把两个逻辑合并到一个约束中,计算出实际总资源消耗后,直接和阈值对比,超出部分施加惩罚,无需再试调单个约束的权重。
总资源消耗公式:总消耗 = 激活场景的运行总数 - 完全覆盖路线的(运行数-1)总和
对应的约束实现:
Constraint totalResourceLimit(ConstraintFactory constraintFactory) { // 定义资源上限阈值 final long RESOURCE_THRESHOLD = 100; // 计算激活场景的总运行数 UniConstraintStream<SelectedScenario> activeScenarios = constraintFactory.forEach(SelectedScenario.class) .filter(scenario -> scenario.getOnOff().isOnOff()); ConstraintCollector<SelectedScenario, ?, Long> totalRunsCollector = ConstraintCollectors.sumLong(SelectedScenario::getRunCount); // 计算可抵扣的总量:完全覆盖路线的(runCount-1)之和 UniConstraintStream<SelectedRoute> eligibleRoutes = constraintFactory.forEach(SelectedRoute.class) .filter(route -> route.getRouteOnOff().isOnOff()) .join(RouteScenario.class, Joiners.equal(SelectedRoute::getRoute, RouteScenario::getRoute)) .join(SelectedScenario.class, Joiners.equal((route, rs) -> rs.getScenario(), SelectedScenario::getScenario)) .filter((route, rs, scenario) -> scenario.getOnOff().isOnOff()) .groupBy((route, rs, scenario) -> route, countTri()) .filter((route, count) -> route.getRunCount() == count); ConstraintCollector<SelectedRoute, ?, Long> totalCreditCollector = ConstraintCollectors.sumLong(route -> route.getRunCount() - 1); // 聚合两个值并判断是否超限 return activeScenarios.collect(totalRunsCollector) .join(eligibleRoutes.collect(totalCreditCollector)) .filter((totalRuns, totalCredit) -> (totalRuns - totalCredit) > RESOURCE_THRESHOLD) .penalize(BendableScore.ofHard(BENDABLE_SCORE_HARD_LEVELS_SIZE, BENDABLE_SCORE_SOFT_LEVELS_SIZE, 1, 0), (totalRuns, totalCredit) -> (totalRuns - totalCredit) - RESOURCE_THRESHOLD) .asConstraint("totalResourceLimit"); }
方法2:自定义ScoreHolder监控总分
如果不想改动原有约束流结构,可以通过自定义ScoreHolder来记录两个约束的分数贡献,实时计算总和并触发超限惩罚:
- 实现自定义ScoreHolder:
public class CustomResourceScoreHolder extends BendableScoreHolder<BendableScore> { private long runsTotal; private long creditTotal; private final long resourceThreshold; public CustomResourceScoreHolder(boolean constraintMatchEnabled, int hardLevelsSize, int softLevelsSize, long resourceThreshold) { super(constraintMatchEnabled, hardLevelsSize, softLevelsSize); this.resourceThreshold = resourceThreshold; } public void addRunsContribution(long amount) { runsTotal += amount; checkThreshold(); } public void addCreditContribution(long amount) { creditTotal += amount; checkThreshold(); } private void checkThreshold() { long totalConsumption = runsTotal - creditTotal; if (totalConsumption > resourceThreshold) { // 移除之前的超限惩罚,避免重复累加 removeConstraintMatch("totalResourceLimit"); // 添加新的超限惩罚 addHardConstraintMatch(null, "totalResourceLimit", totalConsumption - resourceThreshold); } else { removeConstraintMatch("totalResourceLimit"); } } }
- 修改原有约束,使用自定义ScoreHolder:
// 修改runsOnActiveScenarios约束 Constraint runsOnActiveScenarios(ConstraintFactory constraintFactory) { return constraintFactory.forEach(SelectedScenario.class) .filter(selectedScenario -> selectedScenario.getOnOff().isOnOff()) .penalize((CustomResourceScoreHolder scoreHolder, SelectedScenario scenario) -> { scoreHolder.addRunsContribution(scenario.getRunCount()); return BendableScore.ofSoft(BENDABLE_SCORE_HARD_LEVELS_SIZE, BENDABLE_SCORE_SOFT_LEVELS_SIZE, 2, 1); }) .asConstraint("runsOnActiveScenarios"); } // 修改creditRouteRunReduction约束 Constraint creditRouteRunReduction(ConstraintFactory constraintFactory) { return constraintFactory.forEach(SelectedRoute.class) .filter(selectedRoute -> selectedRoute.getRouteOnOff().isOnOff()) .join(RouteScenario.class, Joiners.equal((selectedRoute) -> selectedRoute.getRoute(), RouteScenario::getRoute)) .join(SelectedScenario.class, Joiners.equal((selectedRoute, routeScenario) -> routeScenario.getScenario(), SelectedScenario::getScenario)) .filter((selectedRoute, routeScenario, selectedScenario) -> selectedScenario.getOnOff().isOnOff()) .groupBy((selectedRoute, routeScenario, selectedScenario) -> selectedRoute, countTri()) .filter((selectedRoute, count) -> selectedRoute.getRunCount() == count) .reward((CustomResourceScoreHolder scoreHolder, SelectedRoute route, Long count) -> { scoreHolder.addCreditContribution(route.getRunCount() - 1); return BendableScore.ofSoft(BENDABLE_SCORE_HARD_LEVELS_SIZE, BENDABLE_SCORE_SOFT_LEVELS_SIZE, 2, 1); }) .asConstraint("creditRouteRunReduction"); }
- 在Solver配置中指定自定义ScoreHolder:
SolverFactory<YourSolution> solverFactory = SolverFactory.create(new SolverConfig() .withSolutionClass(YourSolution.class) .withEntityClasses(SelectedScenario.class, SelectedRoute.class) .withScoreDirectorFactory(new ScoreDirectorFactoryConfig() .withScoreDefinitionClass(BendableScoreDefinition.class) .withHardLevelsSize(BENDABLE_SCORE_HARD_LEVELS_SIZE) .withSoftLevelsSize(BENDABLE_SCORE_SOFT_LEVELS_SIZE) .withScoreHolderClass(CustomResourceScoreHolder.class)));
方法3:用全局约束聚合计算(推荐)
OptaPlanner的ConstraintCollector支持全局聚合操作,可以在一个约束中完成两个部分的计算和超限判断,完全符合约束流的设计范式:
Constraint totalResourceConstraint(ConstraintFactory constraintFactory) { final long RESOURCE_THRESHOLD = 100; // 聚合激活场景的总运行数 UniConstraintStream<Long> totalRunsStream = constraintFactory.forEach(SelectedScenario.class) .filter(scenario -> scenario.getOnOff().isOnOff()) .collect(ConstraintCollectors.sumLong(SelectedScenario::getRunCount)); // 聚合完全覆盖路线的抵扣总和 UniConstraintStream<Long> totalCreditStream = constraintFactory.forEach(SelectedRoute.class) .filter(route -> route.getRouteOnOff().isOnOff()) .join(RouteScenario.class, Joiners.equal(SelectedRoute::getRoute, RouteScenario::getRoute)) .join(SelectedScenario.class, Joiners.equal((route, rs) -> rs.getScenario(), SelectedScenario::getScenario)) .filter((route, rs, scenario) -> scenario.getOnOff().isOnOff()) .groupBy((route, rs, scenario) -> route, countTri()) .filter((route, count) -> route.getRunCount() == count) .collect(ConstraintCollectors.sumLong((route, count) -> route.getRunCount() - 1)); // 合并两个聚合结果,判断超限并惩罚 return totalRunsStream.join(totalCreditStream) .filter((totalRuns, totalCredit) -> (totalRuns - totalCredit) > RESOURCE_THRESHOLD) .penalize(BendableScore.ofHard(BENDABLE_SCORE_HARD_LEVELS_SIZE, BENDABLE_SCORE_SOFT_LEVELS_SIZE, 1, 0), (totalRuns, totalCredit) -> (totalRuns - totalCredit) - RESOURCE_THRESHOLD) .asConstraint("totalResourceLimit"); }
关键注意事项
- 由于你的问题属于重叠集合覆盖问题(无凸性),建议使用线性惩罚(超出阈值多少就罚多少),避免非线性惩罚导致求解器陷入局部最优。
- 根据需求选择约束类型:如果资源上限是必须遵守的硬规则,用
ofHard;如果允许超限但需要优先规避,用ofSoft,匹配你已有的可弯曲分数配置。
内容的提问来源于stack exchange,提问作者user2952819
相关产品推荐
相关产品推荐

