Optaplanner:如何在仓库等待当天晚些时候访问时间窗口?

时间:2017-08-09 05:29:09

标签: optaplanner

我在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;
        }
    }
}

我在想,在更新到达时间时,我不知何故需要检测它是否是第一次正在更新的访问,然后向后计算一天以找到合适的出发时间,从时间窗口考虑所有准备时间。

但这必须是一个普遍的问题。有更好的解决方案吗?

2 个答案:

答案 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);可以计算出从仓库离开的时间可以延迟多少分钟,以便最大限度地减少每个客户的等待时间,但不会超时时间窗口。