OptaPlanner- TSPTW最小化总时间

时间:2015-10-18 05:40:25

标签: optaplanner

我正在使用OptaPlanner来解决实际上是时间窗的旅行商问题(TSPTW)。我有一个基于OptaPlanner提供VRPTW示例的工作初始解决方案。

我现在正试图解决偏离标准TSPTW的要求,这些要求是:

  1. 我正在尽量减少花费的总时间而不是总行程距离。因此,空闲时间对我不利。
  2. 除了标准时间窗口访问之外,我还必须支持不迟于(NLT)访问(即不在X时间之后访问)和不早于(NET)访问(即不要&#) 39;在X时间之前访问。)
  3. 我目前的解决方案始终将首次访问的到达时间设置为该访问的开始时间。对于我的要求,这有以下问题:

    1. 这会引入不必要的空闲时间,如果访问是在时间窗口的某个时间到达的话,可以避免这种情况。
    2. NLT的行为是有问题的。如果我定义一个NLT,其开始时间设置为Long.MIN_VALUE(表示它是无界的,而不是诉诸于空)那么这就是NLT访问到达的时间(与#1相同的问题)。我尝试通过将开始时间设置为NLT时间来解决此问题。这导致及时到达NLT访问,但超过了后续访问的时间窗口。
    3. 我该如何解决这个/这些问题?我怀疑解决方案将涉及ArrivalTimeUpdatingVariableListener,但我不知道该解决方案应该是什么样的。

      如果相关,我已粘贴在我目前的评分规则中。需要注意的一点是"距离"真的是旅行时间。此外,由于域名原因,我鼓励NLT和NET到达时间接近截止时间(NLT的结束时间,NET的开始时间)。

      import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScoreHolder;
      
      global HardSoftLongScoreHolder scoreHolder;
      
      // Hard Constraints
      rule "ArrivalAfterWindowEnd"
        when
          Visit(arrivalTime > maxStartTime, $arrivalTime : arrivalTime, $maxStartTime : maxStartTime)
        then
          scoreHolder.addHardConstraintMatch(kcontext, $maxStartTime - $arrivalTime);
      end
      
      // Soft Constraints
      rule "MinimizeDistanceToPreviousEvent"
        when
          Visit(previousRouteEvent != null, $distanceFromPreviousRouteEvent : distanceFromPreviousRouteEvent)
        then
          scoreHolder.addSoftConstraintMatch(kcontext, -$distanceFromPreviousRouteEvent);
      end
      
      rule "MinimizeDistanceFromLastEventToHome"
        when
          $visit : Visit(previousRouteEvent != null)
          not Visit(previousRouteEvent == $visit)
          $home : Home()
        then
          scoreHolder.addSoftConstraintMatch(kcontext, -$visit.getDistanceTo($home));
      end
      
      rule "MinimizeIdle"
        when
          Visit(scheduleType != ScheduleType.NLT, arrivalTime < minStartTime, $minStartTime : minStartTime, $arrivalTime : arrivalTime)
        then
          scoreHolder.addSoftConstraintMatch(kcontext, $arrivalTime - $minStartTime);
      end
      
      rule "PreferLatestNLT"
        when
          Visit(scheduleType == ScheduleType.NLT, arrivalTime < maxStartTime, $maxStartTime : maxStartTime, $arrivalTime : arrivalTime)
        then
          scoreHolder.addSoftConstraintMatch(kcontext, $arrivalTime - $maxStartTime);
      end
      
      rule "PreferEarliestNET"
        when
          Visit(scheduleType == ScheduleType.NET, arrivalTime > minStartTime, $minStartTime : minStartTime, $arrivalTime : arrivalTime)
        then
          scoreHolder.addSoftConstraintMatch(kcontext, $minStartTime - $arrivalTime);
      end
      

2 个答案:

答案 0 :(得分:0)

要查看使用实际道路时间而非道路距离的示例:在示例应用中,打开车辆路线,单击按钮导入,加载文件roaddistance/capacitated/belgium-road-time-n50-k10.vrp。那些时间是calculated with GraphHopper

要查看使用Time Windows的示例,请打开Vehicle Routing并快速打开名为cvrptw的数据集(tw代表Time Windows)。如果你看一下CVRPTW的学术规范(链接自文档第3章IIRC),你会发现它已经有一个严格的约束“不要在时间窗口关闭后到达” - 所以你会看到得分规则中的一个。至于到达太早(因此失去空闲时间):复制粘贴硬约束,使其变软,使其使用readyTime而不是dueTime并反转它的比较和惩罚计算。我实际上最初实现了(因为这是合乎逻辑的事情),但因为我遵循学术规范(与学术成果相比),我不得不将其删除。

答案 1 :(得分:0)

我能够通过修改ArrivalTimeUpdatingVariableListener的updateArrivalTime方法来解决我的问题,以便向后移动并(尝试)移动先前的到达时间。另外,我引入了一个getPreferredStartTime()方法来尽可能地支持NLT事件默认。最后,为了代码清洁,我将updateArrivalTime方法从ArrivalTimeUpdatingVariableListener移到了Visit类中。

以下是Visit类的相关代码:

public long getPreferredStartTime()
{
    switch(scheduleType)
    {
        case NLT:
            return getMaxStartTime();
        default:
            return getMinStartTime();
    }
}

public Long getStartTime()
{
    Long arrivalTime = getArrivalTime();
    if (arrivalTime == null)
    {
        return null;
    }

    switch(scheduleType)
    {
        case NLT:
            return arrivalTime;
        default:
            return Math.max(arrivalTime, getMinStartTime());
    }
}

public Long getEndTime()
{
    Long startTime = getStartTime();
    if (startTime == null)
    {
        return null;
    }
    return startTime + duration;
}

public void updateArrivalTime(ScoreDirector scoreDirector)
{
    if(previousRouteEvent instanceof Visit)
    {
        updateArrivalTime(scoreDirector, (Visit)previousRouteEvent);
        return;
    }

    long arrivalTime = getPreferredStartTime();
    if(Utilities.equal(this.arrivalTime, arrivalTime))
    {
        return;
    }

    setArrivalTime(scoreDirector, arrivalTime);
}

private void updateArrivalTime(ScoreDirector scoreDirector, Visit previousVisit)
{
    long departureTime = previousVisit.getEndTime();
    long arrivalTime = departureTime + getDistanceFromPreviousRouteEvent();

    if(Utilities.equal(this.arrivalTime, arrivalTime))
    {
        return;
    }

    if(arrivalTime > maxStartTime)
    {
        if(previousVisit.shiftTimeLeft(scoreDirector, arrivalTime - maxStartTime))
        {
            return;
        }
    }
    else if(arrivalTime < minStartTime)
    {
        if(previousVisit.shiftTimeRight(scoreDirector, minStartTime - arrivalTime))
        {
            return;
        }
    }

    setArrivalTime(scoreDirector, arrivalTime);
}

/**
 * Set the arrival time and propagate the change to any following entities.
 */
private void setArrivalTime(ScoreDirector scoreDirector, long arrivalTime)
{
    scoreDirector.beforeVariableChanged(this, "arrivalTime");
    this.arrivalTime = arrivalTime;
    scoreDirector.afterVariableChanged(this, "arrivalTime");

    Visit nextEntity = getNextVisit();
    if(nextEntity != null)
    {
        nextEntity.updateArrivalTime(scoreDirector, this);
    }
}

/**
 * Attempt to shift the arrival time backward by the specified amount.
 * @param requested The amount of time that should be subtracted from the arrival time.
 * @return Returns true if the arrival time was changed.
 */
private boolean shiftTimeLeft(ScoreDirector scoreDirector, long requested)
{
    long available = arrivalTime - minStartTime;
    if(available <= 0)
    {
        return false;
    }

    requested = Math.min(requested, available);
    if(previousRouteEvent instanceof Visit)
    {
        //Arrival time is inflexible as this is not the first event. Forward to previous event.
        return ((Visit)previousRouteEvent).shiftTimeLeft(scoreDirector, requested);
    }

    setArrivalTime(scoreDirector, arrivalTime - requested);
    return true;
}

/**
 * Attempt to shift the arrival time forward by the specified amount.
 * @param requested The amount of time that should be added to the arrival time.
 * @return Returns true if the arrival time was changed.
 */
private boolean shiftTimeRight(ScoreDirector scoreDirector, long requested)
{
    long available = maxStartTime - arrivalTime;
    if(available <= 0)
    {
        return false;
    }

    requested = Math.min(requested, available);
    if(previousRouteEvent instanceof Visit)
    {
        //Arrival time is inflexible as this is not the first event. Forward to previous event.
        //Note, we could start later anyways but that won't decrease idle time, which is the purpose of shifting right
        return ((Visit)previousRouteEvent).shiftTimeRight(scoreDirector, requested);
    }

    setArrivalTime(scoreDirector, arrivalTime + requested);
    return false;
}