Java 用Optaplanner求解VRPTWPD

Java 用Optaplanner求解VRPTWPD,java,drools,optaplanner,Java,Drools,Optaplanner,我是optaplanner的新手,我希望用它来解决收货和送货的VRPTW问题(VRPTWPD) 我从回购协议的例子开始。我正试图增加它来解决我的问题。但是,我无法返回满足优先/车辆约束的解决方案(提货必须在交付前完成,并且两者必须由同一车辆完成) 我始终返回一个解决方案,其中硬分数是我对此类解决方案的期望值(即,我可以在一个小样本问题中将所有违规行为相加,并查看硬分数与我为这些违规行为指定的惩罚相匹配) 我尝试的第一种方法是遵循杰弗里·德斯米特在这里概述的步骤- 每个Customer都有一个变量

我是optaplanner的新手,我希望用它来解决收货和送货的VRPTW问题(VRPTWPD)

我从回购协议的例子开始。我正试图增加它来解决我的问题。但是,我无法返回满足优先/车辆约束的解决方案(提货必须在交付前完成,并且两者必须由同一车辆完成)

我始终返回一个解决方案,其中硬分数是我对此类解决方案的期望值(即,我可以在一个小样本问题中将所有违规行为相加,并查看硬分数与我为这些违规行为指定的惩罚相匹配)

我尝试的第一种方法是遵循杰弗里·德斯米特在这里概述的步骤-

每个
Customer
都有一个变量
customerType
,用于描述是取货(PU)还是送货(DO)。它还有一个名为
parcelId
的变量,用于指示要提取或交付的包裹

我在名为
parcelIdsOnboard
Customer
中添加了一个shadow变量。这是一个哈希集,用于保存驾驶员访问给定
客户时随身携带的所有包裹ID

protected void updateVehicle(ScoreDirector scoreDirector, TimeWindowedCustomer sourceCustomer) {
    Standstill previousStandstill = sourceCustomer.getPreviousStandstill();
    Integer departureTime = (previousStandstill instanceof TimeWindowedCustomer)
            ? ((TimeWindowedCustomer) previousStandstill).getDepartureTime() : null;

    TimeWindowedCustomer shadowCustomer = sourceCustomer;
    Integer arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
    while (shadowCustomer != null && ObjectUtils.notEqual(shadowCustomer.getArrivalTime(), arrivalTime)) {
        scoreDirector.beforeVariableChanged(shadowCustomer, "arrivalTime");
        scoreDirector.beforeVariableChanged(((TimeWindowedCustomer) shadowCustomer).getCounterpartCustomer(), "arrivalTime");
        shadowCustomer.setArrivalTime(arrivalTime);
        scoreDirector.afterVariableChanged(shadowCustomer, "arrivalTime");
        scoreDirector.afterVariableChanged(((TimeWindowedCustomer) shadowCustomer).getCounterpartCustomer(), "arrivalTime");
        departureTime = shadowCustomer.getDepartureTime();
        shadowCustomer = shadowCustomer.getNextCustomer();
        arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
    }
}
我的
VariableListener
保持
parcelIdsOnboard
更新,如下所示:

public void afterEntityAdded(ScoreDirector scoreDirector, Customer customer) {
    if (customer instanceof TimeWindowedCustomer) {
        updateParcelsOnboard(scoreDirector, (TimeWindowedCustomer) customer);
    }
}

public void afterVariableChanged(ScoreDirector scoreDirector, Customer customer) {
    if (customer instanceof TimeWindowedCustomer) {
        updateParcelsOnboard(scoreDirector, (TimeWindowedCustomer) customer);
    }
}

protected void updateParcelsOnboard(ScoreDirector scoreDirector, TimeWindowedCustomer sourceCustomer) {
    Standstill previousStandstill = sourceCustomer.getPreviousStandstill();
    Set<Integer> parcelIdsOnboard = (previousStandstill instanceof TimeWindowedCustomer)
            ? new HashSet<Integer>(((TimeWindowedCustomer) previousStandstill).getParcelIdsOnboard()) : new HashSet<Integer>();

    TimeWindowedCustomer shadowCustomer = sourceCustomer;
    while (shadowCustomer != null) {
        updateParcelIdsOnboard(parcelIdsOnboard, shadowCustomer);
        scoreDirector.beforeVariableChanged(shadowCustomer, "parcelIdsOnboard");
        shadowCustomer.setParcelIdsOnboard(parcelIdsOnboard);
        scoreDirector.afterVariableChanged(shadowCustomer, "parcelIdsOnboard");
        shadowCustomer = shadowCustomer.getNextCustomer();
    }
}

private void updateParcelIdsOnboard(Set<Integer> parcelIdsOnboard, TimeWindowedCustomer customer) {
    if (customer.getCustomerType() == Customer.PICKUP) {
        parcelIdsOnboard.add(customer.getParcelId());
    } else if (customer.getCustomerType() == Customer.DELIVERY) {
        parcelIdsOnboard.remove(customer.getParcelId());
    } else {
        // TODO: throw an assertion
    }
}
对于我的示例问题,我总共创建了6个
Customer
对象(3个皮卡和3个配送)。我的车队规模是12辆

当我运行这个时,我始终得到一个硬分数-3000,这与我看到两辆车被使用时的输出相匹配。一辆车负责所有提货,一辆车负责所有交货

第二种方法我使用的是给每个
客户
一个对其对应
客户
对象的引用(例如,包裹1的收货
客户
有对包裹1的交货
客户
的引用,反之亦然)

然后,我实施了以下规则以强制地块位于同一车辆中(注意:不完全实施优先约束)

对于同一个样本问题,该问题的得分始终为-3000,并且与上述问题的解决方案相同

我已尝试在
FULL\u ASSERT
模式下运行这两个规则。使用
parcelIdsOnboard
的规则不会触发任何异常。但是,规则
“pudoInSameVehicle”
会触发以下异常(在
快速断言
模式下不会触发)

我不知道为什么这是腐败,任何建议将不胜感激

有趣的是,这两种方法都产生了相同(不正确)的解决方案。我希望有人能对下一步的尝试提出一些建议。谢谢

更新:

在深入研究以完整断言模式触发的断言之后,我意识到问题在于拾取和交付客户的依赖性。也就是说,如果您采取的行动消除了对送货
客户的硬罚款
,您还必须消除与收货
客户
相关的罚款。为了保持这些同步,我更新了my
VehicleUpdategVariableListener
和my
ArrivalTimeUpdateingVariableListener
以触发对这两个
Customer
对象的分数计算回调。以下是更新后的
updatehicle
方法,用于触发刚刚移动的
Customer
和对应的
Customer
的分数计算

protected void updateVehicle(ScoreDirector scoreDirector, TimeWindowedCustomer sourceCustomer) {
    Standstill previousStandstill = sourceCustomer.getPreviousStandstill();
    Integer departureTime = (previousStandstill instanceof TimeWindowedCustomer)
            ? ((TimeWindowedCustomer) previousStandstill).getDepartureTime() : null;

    TimeWindowedCustomer shadowCustomer = sourceCustomer;
    Integer arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
    while (shadowCustomer != null && ObjectUtils.notEqual(shadowCustomer.getArrivalTime(), arrivalTime)) {
        scoreDirector.beforeVariableChanged(shadowCustomer, "arrivalTime");
        scoreDirector.beforeVariableChanged(((TimeWindowedCustomer) shadowCustomer).getCounterpartCustomer(), "arrivalTime");
        shadowCustomer.setArrivalTime(arrivalTime);
        scoreDirector.afterVariableChanged(shadowCustomer, "arrivalTime");
        scoreDirector.afterVariableChanged(((TimeWindowedCustomer) shadowCustomer).getCounterpartCustomer(), "arrivalTime");
        departureTime = shadowCustomer.getDepartureTime();
        shadowCustomer = shadowCustomer.getNextCustomer();
        arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
    }
}
这解决了我在第二种方法中遇到的分数腐败问题,并且在一个小样本问题上,生成了一个满足所有硬约束的解决方案(即,该解决方案的硬分数为0)

接下来,我尝试运行一个更大的问题(约380个客户),但解决方案的得分非常低。我尝试了1分钟、5分钟和15分钟来寻找解决方案。分数似乎随着运行时间的增加而线性提高。但是,15分钟后,解决方案仍然很糟糕,似乎需要运行至少一个小时才能产生可行的解决方案。 我需要这个在5-10分钟内运行最多

我知道了。我的理解是,您可以运行一个函数来检查您即将进行的移动是否会导致打破内置的硬约束,如果会,则跳过此移动

这意味着您不必重新运行分数计算,也不必探索那些您知道不会产生成果的分支。例如,在我的问题中,我不希望您能够将
客户
移动到
车辆
,除非其对应方已分配给该车辆或根本未分配车辆

下面是我实现的用于检查该问题的过滤器。它只在ChangeMoves中运行,但我想我也需要它来为swapmove实现类似的功能

public class PrecedenceFilterChangeMove implements SelectionFilter<ChangeMove> { 

    @Override
    public boolean accept(ScoreDirector scoreDirector, ChangeMove selection) {
        TimeWindowedCustomer customer = (TimeWindowedCustomer)selection.getEntity();
        if (customer.getCustomerType() == Customer.DELIVERY) {
            if (customer.getCounterpartCustomer().getVehicle() == null) {
                return true;
            }
            return customer.getVehicle() == customer.getCounterpartCustomer().getVehicle();
        }
        return true;
    }
}
前置过滤器wapmove

@Override
public boolean accept(ScoreDirector scoreDirector, ChangeMove selection) {
    TimeWindowedCustomer customer = (TimeWindowedCustomer)selection.getEntity();
    if (customer.getCustomerType() == Customer.DELIVERY) {
        if (customer.getCounterpartCustomer().getVehicle() == null) {
            return true;
        }
        return selection.getToPlanningValue() == customer.getCounterpartCustomer().getVehicle();
    } 
    return true;
}
@Override
public boolean accept(ScoreDirector scoreDirector, SwapMove selection) {
    TimeWindowedCustomer leftCustomer = (TimeWindowedCustomer)selection.getLeftEntity();
    TimeWindowedCustomer rightCustomer = (TimeWindowedCustomer)selection.getRightEntity();
    if (rightCustomer.getCustomerType() == Customer.DELIVERY || leftCustomer.getCustomerType() == Customer.DELIVERY) {      
        return rightCustomer.getVehicle() == leftCustomer.getCounterpartCustomer().getVehicle() ||
                leftCustomer.getVehicle() == rightCustomer.getCounterpartCustomer().getVehicle();
    }
    return true;
}

有一个,这是有效的。我们还没有一个完善的开箱即用的例子,但我们已经在长期的路线图上了。

这个问题相当长。有什么方法可以总结吗?@GeoffreyDeSmet随着我所做的改变,这个问题变得越来越重要。正如标题所述,我正试图用optaplanner解决VRPTWPD问题。我在另一篇文章中首先采纳了你的建议,但我认为这不是一个好方法。我已经想出了另一种可行的方法,但速度很慢。此时,我正试图找出如何编写一个自定义move类,该类使用CompositeMove移动成对的客户(提货/送货),但运气不太好。你能给我举个例子吗?请慢慢量化:多少个实体/值给出了每秒的平均计算计数?若要使任何VRP超过1000个实体,并且仍能适当扩展,则需要nearbySelection(从6.2.0.CR1和CR2开始新增)
@Override
public boolean accept(ScoreDirector scoreDirector, ChangeMove selection) {
    TimeWindowedCustomer customer = (TimeWindowedCustomer)selection.getEntity();
    if (customer.getCustomerType() == Customer.DELIVERY) {
        if (customer.getCounterpartCustomer().getVehicle() == null) {
            return true;
        }
        return selection.getToPlanningValue() == customer.getCounterpartCustomer().getVehicle();
    } 
    return true;
}
@Override
public boolean accept(ScoreDirector scoreDirector, SwapMove selection) {
    TimeWindowedCustomer leftCustomer = (TimeWindowedCustomer)selection.getLeftEntity();
    TimeWindowedCustomer rightCustomer = (TimeWindowedCustomer)selection.getRightEntity();
    if (rightCustomer.getCustomerType() == Customer.DELIVERY || leftCustomer.getCustomerType() == Customer.DELIVERY) {      
        return rightCustomer.getVehicle() == leftCustomer.getCounterpartCustomer().getVehicle() ||
                leftCustomer.getVehicle() == rightCustomer.getCounterpartCustomer().getVehicle();
    }
    return true;
}