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

OptaPlanner VRP中用Constraint Streams实现insertLogical的方法问询

在Constraint Streams中实现类似insertLogical的逻辑或外连接方案

一、用leftJoin实现外连接(覆盖首次配送场景)

Constraint Streams支持leftJoin操作,正好能解决你没法包含首次配送的问题。具体思路是:

  • 把同一客户的所有配送按时间排序,用leftJoin将每个配送和它的前序配送关联(首次配送的前序为null)
  • 对于前序为null的首次配送,直接用当前储罐状态计算需求;对于有前序的配送,用间隔天数×消耗速率计算需求

示例代码结构:

Constraint demandCalculation(ConstraintFactory constraintFactory) {
    return constraintFactory.forEach(Delivery.class)
            .groupBy(Delivery::getCustomer, Collectors.toList())
            .flatMap((customer, deliveryList) -> {
                // 按配送时间排序
                List<Delivery> sortedDeliveries = deliveryList.stream()
                        .sorted(Comparator.comparing(Delivery::getDeliveryTime))
                        .toList();
                // 生成每个配送与前序的配对,首次配送前序为null
                List<Tuple2<Delivery, Delivery>> pairs = new ArrayList<>();
                for (int i = 0; i < sortedDeliveries.size(); i++) {
                    Delivery current = sortedDeliveries.get(i);
                    Delivery previous = i == 0 ? null : sortedDeliveries.get(i - 1);
                    pairs.add(Tuple2.of(current, previous));
                }
                return pairs;
            })
            .filter((current, previous) -> current.isScheduled())
            .penalize("Demand calculation mismatch", HardSoftScore.ONE_HARD,
                    (current, previous) -> {
                        double expectedDemand;
                        if (previous == null) {
                            // 首次配送:用当前储罐剩余量计算需要补充的量
                            expectedDemand = current.getCustomer().getTankCapacity() - current.getCustomer().getCurrentTankLevel();
                        } else {
                            // 非首次配送:计算间隔天数×消耗速率
                            long daysBetween = ChronoUnit.DAYS.between(previous.getDeliveryTime(), current.getDeliveryTime());
                            expectedDemand = daysBetween * current.getCustomer().getConsumptionRate();
                        }
                        // 对比实际配送量和预期需求,返回差值作为惩罚
                        return Math.abs(current.getDeliveryQuantity() - expectedDemand);
                    });
}

二、用ifExists模拟insertLogical的逻辑

如果你需要类似DRL中insertLogical的“条件满足时激活约束”的效果,可以用Constraint Streams的ifExists操作:

  • 对于非首次配送,检查是否存在前序配送,存在时激活需求计算约束
  • 对于首次配送,直接激活基于当前状态的需求约束

示例代码:

Constraint firstDeliveryDemand(ConstraintFactory constraintFactory) {
    // 首次配送:无前序配送的情况
    return constraintFactory.forEach(Delivery.class)
            .ifNotExists(Delivery.class,
                    Joiners.equal(Delivery::getCustomer),
                    Joiners.lessThan(Delivery::getDeliveryTime))
            .penalize("First delivery demand mismatch", HardSoftScore.ONE_HARD,
                    delivery -> {
                        double expected = delivery.getCustomer().getTankCapacity() - delivery.getCustomer().getCurrentTankLevel();
                        return Math.abs(delivery.getDeliveryQuantity() - expected);
                    });
}

Constraint subsequentDeliveryDemand(ConstraintFactory constraintFactory) {
    // 非首次配送:存在前序配送的情况
    return constraintFactory.forEach(Delivery.class)
            .ifExists(Delivery.class,
                    Joiners.equal(Delivery::getCustomer),
                    Joiners.lessThan(Delivery::getDeliveryTime))
            .join(Delivery.class,
                    Joiners.equal(Delivery::getCustomer),
                    Joiners.lessThan(second(Delivery::getDeliveryTime), first(Delivery::getDeliveryTime)))
            .groupBy(first(Delivery.class), Collectors.maxBy(Comparator.comparing(Delivery::getDeliveryTime)))
            .penalize("Subsequent delivery demand mismatch", HardSoftScore.ONE_HARD,
                    (current, previousOpt) -> {
                        Delivery previous = previousOpt.orElseThrow();
                        long daysBetween = ChronoUnit.DAYS.between(previous.getDeliveryTime(), current.getDeliveryTime());
                        double expected = daysBetween * current.getCustomer().getConsumptionRate();
                        return Math.abs(current.getDeliveryQuantity() - expected);
                    });
}

三、替代预调度配送的简化方案

如果你之前用预调度配送的方式遇到车辆限制问题,可以改为:

  • 给每个客户的“虚拟首次配送”设置一个固定的、早于所有规划时间的时间点,且不分配车辆
  • 在约束中直接将这个虚拟配送作为首次配送的前序,这样所有配送都有配对,无需处理null情况
  • 同时添加约束确保这个虚拟配送不会被实际调度(比如固定其状态为已完成,不参与车辆分配)

这样既避免了外连接的问题,又不用处理复杂的车辆限制逻辑。

内容的提问来源于stack exchange,提问作者elk-tamer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 03:50:45