Java 用Optaplanner求解VRPTWPD
我是optaplanner的新手,我希望用它来解决收货和送货的VRPTW问题(VRPTWPD) 我从回购协议的例子开始。我正试图增加它来解决我的问题。但是,我无法返回满足优先/车辆约束的解决方案(提货必须在交付前完成,并且两者必须由同一车辆完成) 我始终返回一个解决方案,其中硬分数是我对此类解决方案的期望值(即,我可以在一个小样本问题中将所有违规行为相加,并查看硬分数与我为这些违规行为指定的惩罚相匹配) 我尝试的第一种方法是遵循杰弗里·德斯米特在这里概述的步骤- 每个Java 用Optaplanner求解VRPTWPD,java,drools,optaplanner,Java,Drools,Optaplanner,我是optaplanner的新手,我希望用它来解决收货和送货的VRPTW问题(VRPTWPD) 我从回购协议的例子开始。我正试图增加它来解决我的问题。但是,我无法返回满足优先/车辆约束的解决方案(提货必须在交付前完成,并且两者必须由同一车辆完成) 我始终返回一个解决方案,其中硬分数是我对此类解决方案的期望值(即,我可以在一个小样本问题中将所有违规行为相加,并查看硬分数与我为这些违规行为指定的惩罚相匹配) 我尝试的第一种方法是遵循杰弗里·德斯米特在这里概述的步骤- 每个Customer都有一个变量
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”
会触发以下异常(在快速断言
模式下不会触发)
我不知道为什么这是腐败,任何建议将不胜感激
有趣的是,这两种方法都产生了相同(不正确)的解决方案。我希望有人能对下一步的尝试提出一些建议。谢谢
更新:
在深入研究以完整断言模式触发的断言之后,我意识到问题在于拾取和交付客户的依赖性。也就是说,如果您采取的行动消除了对送货客户的硬罚款
,您还必须消除与收货客户
相关的罚款。为了保持这些同步,我更新了myVehicleUpdategVariableListener
和myArrivalTimeUpdateingVariableListener
以触发对这两个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;
}