我正在使用OptaPlanner来解决实际上是时间窗的旅行商问题(TSPTW)。我有一个基于OptaPlanner提供VRPTW示例的工作初始解决方案。
我现在正试图解决偏离标准TSPTW的要求,这些要求是:
我目前的解决方案始终将首次访问的到达时间设置为该访问的开始时间。对于我的要求,这有以下问题:
我该如何解决这个/这些问题?我怀疑解决方案将涉及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
答案 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;
}