我在optaplanners源代码树中遵循了CVRPTW的示例,它运行良好。 :)
但是我有一个与我的用例有关的事情。
让我们说一辆车在一天内只有2次访问,并且访问A全天可用,而访问B在下午有一个狭窄的时间窗口。目前,我的最佳解决方案是让车辆提前离开服务A访问A并在访问B时太早到达 - 并等到时间窗口开始。
相反,我希望车辆在车站等待,这样他就不需要在现场等候了。
在访问到达时间我已经注册了一个听众,以便更新到达时间。
@CustomShadowVariable(variableListenerClass = ArrivalTimeUpdatingVariableListener.class,
sources = {
@CustomShadowVariable.Source(variableName = "previousLocation")})
public int getArrivalTime() {
return arrivalTime;
}
public void setArrivalTime(int arrivalTime) {
this.arrivalTime = arrivalTime;
}
我的听众目前看起来像这样:
public class ArrivalTimeUpdatingVariableListener implements VariableListener<Visit> {
@Override
public void beforeEntityAdded(ScoreDirector scoreDirector, Visit entity) {
// Do nothing
}
@Override
public void afterEntityAdded(ScoreDirector scoreDirector, Visit visit) {
updateArrivalTime(scoreDirector, visit);
}
@Override
public void beforeVariableChanged(ScoreDirector scoreDirector, Visit entity) {
// Do nothing
}
@Override
public void afterVariableChanged(ScoreDirector scoreDirector, Visit visit) {
updateArrivalTime(scoreDirector, visit);
}
@Override
public void beforeEntityRemoved(ScoreDirector scoreDirector, Visit entity) {
// Do nothing
}
@Override
public void afterEntityRemoved(ScoreDirector scoreDirector, Visit entity) {
// Do nothing
}
protected void updateArrivalTime(ScoreDirector scoreDirector, Visit sourceVisit) {
WorkPlanSolution solution = (WorkPlanSolution) scoreDirector.getWorkingSolution();
Location previousLocation = sourceVisit.getPreviousLocation();
Integer departureTime = null;
if(previousLocation != null) {
departureTime = previousLocation.getDepartureTime();
}
Visit shadowVisit = sourceVisit;
Integer arrivalTime = calculateArrivalTime(solution, shadowVisit, departureTime);
while (shadowVisit != null && !Objects.equals(shadowVisit.getArrivalTime(), arrivalTime)) {
scoreDirector.beforeVariableChanged(shadowVisit, "arrivalTime");
shadowVisit.setArrivalTime(arrivalTime);
scoreDirector.afterVariableChanged(shadowVisit, "arrivalTime");
departureTime = shadowVisit.getDepartureTime();
shadowVisit = shadowVisit.getNextVisit();
arrivalTime = calculateArrivalTime(solution, shadowVisit, departureTime);
}
}
private Integer calculateArrivalTime(WorkPlanSolution solution, Visit visit, Integer previousDepartureTime) {
if (visit == null || visit.getLocation()== null) {
return 0;
}
int distanceToPreviousInSeconds = 0;
if(visit.getPreviousLocation() != null) {
distanceToPreviousInSeconds = solution.getDistanceMatrix().getDistanceBetween(visit.getPreviousLocation().getLocation(),
visit.getLocation());
} else if(visit.getWorkPlan() != null) {
distanceToPreviousInSeconds = solution.getDistanceMatrix().getDistanceBetween(visit.getWorkPlan().getLocation(), visit.getLocation());
}
int distanceToPreviousInMinutes = distanceToPreviousInSeconds / 60;
if (previousDepartureTime == null) {
// PreviousStandstill is the Vehicle, so we leave from the Depot at the best suitable time
return Math.max(visit.getReadyTime(), distanceToPreviousInMinutes);
} else {
return previousDepartureTime + distanceToPreviousInMinutes;
}
}
}
我在想,在更新到达时间时,我不知何故需要检测它是否是第一次正在更新的访问,然后向后计算一天以找到合适的出发时间,从时间窗口考虑所有准备时间。
但这必须是一个普遍的问题。有更好的解决方案吗?
答案 0 :(得分:0)
如果访问是第一个客户(所以if previousStandstill instanceof Vehicle
),那么到达时间应该是max(readyTime, previousStandstillWhichIsVehicle.getDepotOpenTime() + drivingTime)
。
其余部分就像示例中一样:当访问的到达时间发生变化时(以及它的离开时间也是如此),监听器会对链的其余部分进行迭代以相应地更新这些访问(参见图中的图像)文档)。
答案 1 :(得分:0)
好的,所以我终于设法解决了,尽管我仍然需要优化性能。
在半伪术语中,这是我在updateArrivalTime方法中所做的:
protected void updateArrivalTime(ScoreDirector scoreDirector, Visit sourceVisit) {
WorkPlanSolution solution = (WorkPlanSolution) scoreDirector.getWorkingSolution();
Location previousLocation = sourceVisit.getPreviousLocation();
WorkPlan plan = getWorkPlan(sourceVisit);
Integer prevDepartureTime = null;
if (previousLocation != null) {
prevDepartureTime = previousLocation.getDepartureTime();
}
if(plan == null) {
// No plan found. Just update from this element and downwards
Visit firstLink = sourceVisit;
Integer arrivalTime = calculateArrivalTime(solution, plan, firstLink, prevDepartureTime);
updateChainedArrival(scoreDirector, solution, arrivalTime, plan, firstLink);
} else {
// Plan found. Recalculate from the beginning of the workplan.
plan.resetDepartureTime();
Visit firstLink = plan.getNextVisit();
Integer arrivalTime = calculateArrivalTime(solution, plan, firstLink, plan.getDepartureTime());
updateChainedArrival(scoreDirector, solution, arrivalTime, plan, firstLink);
// Update wait time if needed
int trimableTime = WorkPlanHelper.calculateTrimableWaitTime(plan);
if(trimableTime > 0) {
// Update all arrival times of the workplan again
firstLink = plan.getNextVisit();
plan.setDepartureTime(plan.getDepartureTime() + trimableTime);
arrivalTime = calculateArrivalTime(solution, plan, firstLink, plan.getDepartureTime());
updateChainedArrival(scoreDirector, solution, arrivalTime, plan, firstLink);
}
}
}
如何运作WorkPlanHelper.calculateTrimableWaitTime(plan);
可以计算出从仓库离开的时间可以延迟多少分钟,以便最大限度地减少每个客户的等待时间,但不会超时时间窗口。