Optaplanner - 使用多个规划变量时的链式规划实体 'corruption'

Optaplanner - Chained planning entity 'corruption' when using multiple planning variables

我是 Optaplanner 的新手,我一直在考虑将 VRP 扩展到不同的问题 space。我正在使用 6.1.0-Final。这是一个很难表达的问题,但是这里是:

规划实体 - 装运(延长停滞),由运输船(即货船)锚定 规划变量 - 终端(货物目的地),当然还有隐式运输车(在 VRP 示例中作为影子变量,如车辆)

在这个路由问题中,货物被直接运送到码头,然后返回工厂。即,运输车服务于一个航站楼,然后 return 到达设施。有一队运输车capacities/speeds。终端有一个位置,它定义了终端与设施之间的距离。运输商可以处理多批货物,但受到往返行程时间和每批货物 'scheduled departure window' 的限制。因此,创建了一个以 Transporter 作为锚点的链,链接到它所服务的所有 Shipments。

考虑到这个问题域,我几乎复制了 VRP with time windows 示例。有称为 previousStandstill 和 arrivalTime 的影子变量(这是到达设施而不是终端)。我修改了 ArrivalTimeUpdatingVariableListener,以便它计算从最后一批货物离开返回设施的往返时间。我有严格的限制来确保与装运相关的运输车实际上在预定的出发时间 window 内可用,具有足够的容量等。我还试图根据装运的许多属性来最大化利润,运输车和码头。

我遇到的问题是装运的 arrivalTime 属性与上一次装运的计算到达时间(出发时间 + 旅行时间)之间似乎不匹配。这只发生在一些货件上,而不是全部。由于在到达时间计算中依赖于终端和运输工具,我还尝试添加另一个名为 'previousTerminal' 的影子变量,并使用它来尝试确保针对 Shipment 的 previousTerminal 等于针对 previousStandstill 的终端(使用 TerminalUpdatingVariableListener)。

但是,这似乎也不起作用,我发现 Shipment 实例上的 'previousTerminal' 与 previousStandstill(Shipment)的终端不匹配。同样,这只发生在某些货件上。就好像在没有触发 TerminalUpdatingVariableListener 上的 afterVariableChanged 事件的情况下更新了针对上一次装运的 Terminal 属性。

这是一个示例输出。在这里,您可以看到后面带有“***************不匹配的货件运输时间*********************”的货件有一个 'previous terminal' 与链中先前装运的终端不匹配。

Shipment 00001, amount 150000.0, terminal Terminal G, transporter North Sea LNG 001, previous shipment null, prev shp term Terminal J, lastShipmentTransitTime 0, thisShipmentTransitTime 5844  
Shipment 00002, amount 180000.0, terminal Terminal J, transporter North Sea LNG 004, previous shipment null, prev shp term Terminal J, lastShipmentTransitTime 0, thisShipmentTransitTime 9179  
Shipment 00003, amount 250000.0, terminal Terminal B, transporter North Sea LNG 002, previous shipment null, prev shp term Terminal J, lastShipmentTransitTime 0, thisShipmentTransitTime 6656   
Shipment 00004, amount 180000.0, terminal Terminal J, transporter North Sea LNG 003, previous shipment null, prev shp term Terminal G, lastShipmentTransitTime 0, thisShipmentTransitTime 10199  
Shipment 00005, amount 150000.0, terminal Terminal J, transporter North Sea LNG 006, previous shipment null, prev shp term Terminal J, lastShipmentTransitTime 0, thisShipmentTransitTime 11474  
Shipment 00006, amount 150000.0, terminal Terminal J, transporter North Sea LNG 004, previous shipment 00002, prev shp term Terminal J, lastShipmentTransitTime 9179, thisShipmentTransitTime 9179  
Shipment 00007, amount 100000.0, terminal Terminal J, transporter North Sea LNG 001, previous shipment 00001, prev shp term Terminal G, lastShipmentTransitTime 5844, thisShipmentTransitTime 12239  
Shipment 00008, amount 250000.0, terminal Terminal G, transporter North Sea LNG 002, previous shipment 00003, prev shp term Terminal B, lastShipmentTransitTime 6656, thisShipmentTransitTime 5157  
Shipment 00009, amount 200000.0, terminal Terminal C, transporter North Sea LNG 003, previous shipment 00004, prev shp term Terminal J, lastShipmentTransitTime 10199, thisShipmentTransitTime 11001  
Shipment 00010, amount 150000.0, terminal Terminal E, transporter North Sea LNG 001, previous shipment 00007, prev shp term Terminal G, lastShipmentTransitTime 5844, thisShipmentTransitTime 15085  
***************Mismatching shipment transit times*******************  
Shipment 00011, amount 200000.0, terminal Terminal J, transporter North Sea LNG 003, previous shipment 00009, prev shp term Terminal B, lastShipmentTransitTime 6286, thisShipmentTransitTime 10199  
***************Mismatching shipment transit times*******************  
Shipment 00012, amount 100000.0, terminal Terminal J, transporter North Sea LNG 006, previous shipment 00005, prev shp term Terminal J, lastShipmentTransitTime 11474, thisShipmentTransitTime 11474  
Shipment 00013, amount 250000.0, terminal Terminal G, transporter North Sea LNG 002, previous shipment 00008, prev shp term Terminal G, lastShipmentTransitTime 5157, thisShipmentTransitTime 5157  
Shipment 00014, amount 200000.0, terminal Terminal E, transporter North Sea LNG 003, previous shipment 00011, prev shp term Terminal J, lastShipmentTransitTime 10199, thisShipmentTransitTime 12571  
Shipment 00015, amount 150000.0, terminal Terminal E, transporter North Sea LNG 006, previous shipment 00012, prev shp term Terminal J, lastShipmentTransitTime 11474, thisShipmentTransitTime 14143  
Shipment 00016, amount 150000.0, terminal Terminal J, transporter North Sea LNG 001, previous shipment 00010, prev shp term Terminal E, lastShipmentTransitTime 15085, thisShipmentTransitTime 12239  
Shipment 00017, amount 200000.0, terminal Terminal J, transporter North Sea LNG 003, previous shipment 00014, prev shp term Terminal J, lastShipmentTransitTime 10199, thisShipmentTransitTime 10199  
***************Mismatching shipment transit times*******************  

我花了相当多的时间试图理解这里发生了什么。我已经尝试更改 ShadowVariable 源中的顺序,以及许多其他试错尝试,但似乎没有任何方法可以解决此链“损坏”。

也许是我对这个工具的无知,但我会感激一些 guidance/pointers 因为到目前为止我对我所看到的印象非常深刻。

非常感谢您的支持,请耐心阅读所有内容!

更新 - 按照 'FULL_ASSERT' 模式。现在引发了以下异常(之前没有看到这个)......虽然不确定它是什么意思......

</p> <p>线程"AWT-EventQueue-0"中出现异常java.lang.IllegalStateException:解决失败。 ... Caused by: " java.lang.IllegalStateException: Score corruption: the workingScore (-2957hard/57945soft) is not the uncorruptedScore (-2957hard/57964soft) after completedAction (Shipment 00002, amount 180000.0, terminal Terminal G, transporter North Sea LNG 002,之前的装运为空,上一个 shp term Terminal B, lastShipmentTransitTime 0, thisShipmentTransitTime 5157 => [Terminal-8]): 损坏的 scoreDirector 有 5 个过量的 ConstraintMatch(s): org.optaplanner.examples.shipscheduling.solver/arrivalAfterLoadingWindow/level0/[装运 00003,金额 250000.0,终端 B 航站楼,运输商北海液化天然气 002,上一装运 00002,上一个航站楼 G,lastShipmentTransitTime 5157,thisShipmentTransitTime 6656]=-4456 org.optaplanner.examples.shipscheduling.solver/arrivalAfterLoadingWindow/level0/[装运 00008,金额 250000.0,终端 B 航站楼,运输商北海液化天然气 002,上一个装运 00003,上一个航站楼 B 航站楼,lastShipmentTransitTime 6656,thisShipmentTransitTime 6656]=-112 org.optaplanner.examples.shipscheduling.solver/maximise profit/level1/[装运 00013,金额 250000.0,航站楼 B,运输商北海液化天然气 002,上一次装运 00008,上一个航站楼 B,lastShipmentTransitTime 6656,thisShipmentTransitTime 6656] =4009 org.optaplanner.examples.shipscheduling.solver/maximise profit/level1/[装运 00008,金额 250000.0,航站楼 B,运输商北海液化天然气 002,上一批货 00003,上一个航站楼 B,lastShipmentTransitTime 6656,thisShipmentTransitTime 6656] =10410 org.optaplanner.examples.shipscheduling.solver/terminal 不够大 transporter/level0/[[Terminal-2], [Transporter-2], 货件 00013, 金额 250000.0, 航站楼 B, 运输车 North Sea LNG 002,以前的装运 00008,上一个航站楼 B,上次装运运输时间 6656,这个装运运输时间 6656]=-70000 损坏的 scoreDirector 有 3 个 ConstraintMatch(s) 缺失: org.optaplanner.examples.shipscheduling.solver/arrivalAfterLoadingWindow/level0/[装运 00003,金额 250000.0,终端 B 航站楼,运输商北海液化天然气 002,上一装运 00002,上一个航站楼 G,lastShipmentTransitTime 5157,thisShipmentTransitTime 6656]=-2957 org.optaplanner.examples.shipscheduling.solver/maximise profit/level1/[装运 00008,金额 250000.0,航站楼 B,运输商北海液化天然气 002,上一批货 00003,上一个航站楼 B,lastShipmentTransitTime 6656,thisShipmentTransitTime 6656] =10398 org.optaplanner.examples.shipscheduling.solver/maximise profit/level1/[装运 00013,金额 250000.0,航站楼 B,运输商北海液化天然气 002,上一次装运 00008,上一个航站楼 B,lastShipmentTransitTime 6656,thisShipmentTransitTime 6656] =10316 检查您的分数限制....</p> <p>

更新 - 将 Optaplanner 核心更新为 6.2.0.CR4,启用 'FULL_ASSERT' 时出现不同的异常。如果我禁用到达时间规则检查,那么我将不再看到任何异常……但我很难理解为什么会这样。关于 'undo move' 我需要了解一些具体信息吗?

</p> <p>Caused by: java.lang.IllegalStateException: moveClass (class org.optaplanner.core.impl.heuristic.move.CompositeMove) 的移动 ([Shipment 00009, amount 200000.0, assigned terminal null, maxTerminalCapacity null, and transporter null, capacity null, profit 0, periodStart 18000, periodEnd 18200, arrival time18000, department time = 18000, return time 18000, lastShipmentTransitTime 0, thisShipmentTransitTime 0, previous shipment null => [Transporter-2], 货件 00009, 金额 200000.0 , 指定航站楼 null, maxTerminalCapacity null, and transporter null, capacity null, profit 0, periodStart 18000, periodEnd 18200, arrival time18000, department time = 18000, return time 18000, lastShipmentTransitTime 0, thisShipmentTransitTime 0, previous shipment null = > [Terminal-2]]) 可能有一个损坏的 undoMove ([Shipment 00009, amount 200000.0, assigned terminal null, maxTerminalCapacity null, and transporter null, capacity null, profit 0, periodStart 18000, periodEnd 18200, arrival time18000, departure time = 18000,return 时间 18000,lastShipmentTransitT ime 0, thisShipmentTransitTime 0, previous shipment null => null, Shipment 00009, amount 200000.0, assigned terminal null, maxTerminalCapacity null, and transporter null, capacity null, profit 0, periodStart 18000, periodEnd 18200, arrival time18000, departure time = 18000, return 时间 18000,lastShipmentTransitTime 0,thisShipmentTransitTime 0,上次装运 null => null])。或者可能存在损坏的分数规则。 检查 Move class 的 Move.createUndoMove(...) 方法并启用 EnvironmentMode FULL_ASSERT 以在损坏的分数规则上更快地失败。 分数损坏:lastCompletedStepScore (-6256hard/0soft) 不是 undoScore (-19256hard/0soft)。</p> <p>

更新 - 修复

好的,经过大量挖掘后我发现删除了:

&& ObjectUtils.notEqual(shadowShipment.getArrivalTime(), arrivalTime)

来自

while (shadowShipment != null) && ObjectUtils.notEqual(shadowShipment.getArrivalTime(), arrivalTime)) {

在 ArrivalTimeUpdatingVariableListener 中似乎解决了这个问题,虽然我还不完全确定为什么......我想这会稍微减慢速度。

感谢杰弗里的指点。

这个问题与 while 循环一旦发现到达时间没有改变的货件有关。在某些情况下,shadowShipment 的到达时间必须相同,但链中的后续装运则不同。

我已经更新了 ArrivalTimeUpdatingListener 如下(这很好用):

protected void updateArrivalTime(ScoreDirector scoreDirector, Shipment sourceShipment) {
    // First get the previous shipment and the departure time. 
    // If the the previousStandstill is a transporter then the departure time will be null.
    Standstill previousStandstill = sourceShipment.getPreviousStandstill();
    Integer departureTime = (previousStandstill instanceof Shipment)
            ? ((Shipment) previousStandstill).getDepartureTime() : null;
    Shipment shadowShipment = sourceShipment;
    // Calculate the arrival time of the source shipment based on the departure time of the last.
    Integer arrivalTime = calculateArrivalTime(shadowShipment, departureTime);
    // Loop through each shipment in the chain from the source shipment forwards.
    while (shadowShipment != null) {
        // Only update the arrival time if it has changed.
        if(ObjectUtils.notEqual(shadowShipment.getArrivalTime(), arrivalTime)) {
            scoreDirector.beforeVariableChanged(shadowShipment, "arrivalTime");
            shadowShipment.setArrivalTime(arrivalTime);
            scoreDirector.afterVariableChanged(shadowShipment, "arrivalTime");
        }
        // Set the variables for the next loop.
        departureTime = shadowShipment.getDepartureTime();
        shadowShipment = shadowShipment.getNextShipment();
        arrivalTime = calculateArrivalTime(shadowShipment, departureTime);
    }
}