Hello OptaPlanner社区。 我正在开发Rest API,以计划车队的路线。在寻找一种对我有帮助的工具时,我找到了Optaplanner,并且我发现它有多个成功案例。在第一阶段中,我考虑了最快的距离和车辆的载客量来制定计划。我得到了预期的结果。现在,我正在计划访问和存款的时间窗口,但是我还没有成功。
要求
R1-我有一支车队。每个车辆都具有容量及其存放处,并且该存放处具有时间范围。从用于VRP的OptaPlanner的示例中,我仅对我作为浮动对象处理的容量进行了更改。据我了解,OptaPlanner示例中的所有车辆都移动到一个仓库。在我的情况下,每辆车都有自己的仓库,每辆车都有自己的固定仓库,并且可能有几辆车有相同的仓库。
R2-我有探视服务(送货服务)。每次访问都有需求和时间窗口。从用于VRP的OptaPlanner的示例中,我仅对我将其处理为“浮动”类型的需求进行了一次修改。
在将带有TW的变体添加到我的路由问题的过程中,我有一些疑问和问题,因为我没有通过应用TW来获得解决问题的可行方法:
1-我了解我不需要修改OptaPlanner示例,因此每辆车运输的物品不能超过其容量。我只需要调整约束提供者,以使计算处于浮动状态。我想知道我是否正确吗?另一方面,如何管理尺寸的容量和需求?在OptaPlanner中,它是一个数字,但我需要按体积和重量来管理它。
在OptaPlanner域中,我将车辆的变量“ capacity”和访问的变量“ demand”修改为“ float”。
Constraint vehicleCapacity(ConstraintFactory constraintFactory) {
return constraintFactory.from(PlanningVisit.class)
.groupBy(PlanningVisit::getVehicle, sumBigDecimal(PlanningVisit::getDemand))
.filter((vehicle, demand) -> demand.compareTo(vehicle.getCapacity()) > 0)
.penalizeLong(
"vehicle capacity",
HardSoftLongScore.ONE_HARD,
(vehicle, demand) -> demand.subtract(vehicle.getCapacity()).longValue());
}
2-在OptaPlanner示例中,我了解到TW是一个乘以一千的长整数,但是我不知道该长整数是否表示小时或日期,还是仅将小时换算成分钟并乘以一千。 我正在做的是将TW转换为分钟数并乘以一千,例如,如果是上午8点,则准备时间为对数,等于“ 480000”。 对于服务持续时间,我不会将其乘以1000,而是始终将其视为10分钟。我转换正确吗? ,这是OptaPlanner期望的时间长吗?
3-我知道使用OptaPlanner的时间窗口示例可以解决此要求(R2),而无需进行任何更改,但是由于某种原因,我无法找到并没有给我带来可行的解决方案。例如,它返回了我:花费的时间(5000),最佳成绩(-3313987hard / -4156887soft)。
我曾经认为错误可能是时间窗口日期的转换,或者我可能缺少一些硬约束,因为访问的到达时间不适合为访问或存款定义的时间窗口
例如: 我有4个带时间窗的访问,早上2次(访问2,访问4),下午2次(访问1,访问3)。 我有两辆车,车辆1离开仓库1,该仓库在早晨的时间表中有一个时间窗口,而另一辆汽车离开仓库2,其在下午的时间表中有一个时间窗口。 因此,我希望车辆1进行早上有时间窗口的访问,车辆2进行下午有时间窗口的访问:[车辆1:{访问2,访问4},车辆2:{访问1,访问3}]
我一定做错了什么,但我找不到哪里,不仅不符合押金的TW,而且每次造访的到达时间都超出了定义的TW。我不明白为什么我会有这么大的到达时间,甚至超过了规定的限制1天(所有到达时间都超过1440000 = 1400min = 24 = 12am),也就是说,他们是在这个时间之后到达的。
这是我获得的结果:得分(-3313987hard / -4156887soft)
路线1指车辆1遵循的路线 车辆1
Depot 1 with TW (8:00 a 13:00)
ready_time: 480000
due_time: 780000
Visit 2 (8:30 a 12:30)
ready_time: 510000
due_time: 750000
service_duraration 10 = 10
arrival_time: 1823943
departure_time: 1833943
Visit 4 (9:30 a 12:30)
ready_time: 570000
due_time: 750000
service_duraration 10
arrival_time: 1739284
departure_time: 1739294
Visit 3 (14:40 a 15:30)
ready_time: 880000
due_time: 930000
service_duraration 10
arrival_time: 1150398
departure_time: 1150408
路线2指车辆2遵循的路线 车辆2
Depot 2 with TW (12:00 a 17:00)
ready_time: 720000
due_time: 1020000
Visit 1 (14:00 a 16:30)
ready_time: 840000
due_time: 990000
service_duraration 10 = 10
arrival_time: 2523243
departure_time: 2523253
这是我的代码,它可以为您提供更好的上下文。 这是我的VariableListerner,用于更新阴影变量“到达时间”。我没有做任何修改,但是每次访问返回给我的到达时间都不符合TW。
public class ArrivalTimeUpdatingVariableListener implements VariableListener<PlanningVisit> {
...
protected void updateArrivalTime(ScoreDirector scoreDirector, TimeWindowedVisit sourceCustomer) {
Standstill previousStandstill = sourceCustomer.getPreviousStandstill();
Long departureTime = previousStandstill == null ? null
: (previousStandstill instanceof TimeWindowedVisit)
? ((TimeWindowedVisit) previousStandstill).getDepartureTime()
: ((TimeWindowedDepot) ((PlanningVehicle)
previousStandstill).getDepot()).getReadyTime();
TimeWindowedVisit shadowCustomer = sourceCustomer;
Long arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
while (shadowCustomer != null && !Objects.equals(shadowCustomer.getArrivalTime(),
arrivalTime)) {
scoreDirector.beforeVariableChanged(shadowCustomer, "arrivalTime");
shadowCustomer.setArrivalTime(arrivalTime);
scoreDirector.afterVariableChanged(shadowCustomer, "arrivalTime");
departureTime = shadowCustomer.getDepartureTime();
shadowCustomer = shadowCustomer.getNextVisit();
arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
}
}
private Long calculateArrivalTime(TimeWindowedVisit customer, Long previousDepartureTime) {
if (customer == null || customer.getPreviousStandstill() == null) {
return null;
}
if (customer.getPreviousStandstill() instanceof PlanningVehicle) {
// PreviousStandstill is the Vehicle, so we leave from the Depot at the best suitable time
return Math.max(customer.getReadyTime(),
previousDepartureTime + customer.distanceFromPreviousStandstill());
}
return previousDepartureTime + customer.distanceFromPreviousStandstill();
}
}
此服务是我从数据库中存储的数据中构建域实体的地方(查找)。我在求解器中使用了这个TimeWindowedVehicleRoutingSolution。
public TimeWindowedVehicleRoutingSolution find(UUID jobId) {
//load VRP from DB
RoutingProblem byJobId = routingProblemRepository.findVRP(jobId);
Set<Vehicle> vehicles = byJobId.getVehicles();
Set<Visit> visits = byJobId.getVisits();
//building solution
List<PlanningDepot> planningDepots = new ArrayList<>();
List<PlanningVehicle> planningVehicles = new ArrayList<>();
List<PlanningVisit> planningVisits = new ArrayList<>();
vehicles.forEach(vehicle -> {
//submit to planner location of the deposit, add to matrix for calculating distance
PlanningLocation planningLocation =
optimizer.submitToPlanner(vehicle.getDepot().getLocation());
//Depot, Vehicle and Visit are my persistence JPA entities, they are not the OptaPlanner
domain entities.
//The OptaPlanner domain entities are: PlanningVehicle, PlanningDepot and PlanningVisit
//I build the entities of the optaplanner domain from my persistence entities
Depot depot = vehicle.getDepot();
TimeWindowedDepot timeWindowedDepot = new TimeWindowedDepot();
TimeWindowedDepot timeWindowedDepot = new TimeWindowedDepot(depot.getId(),
planningLocation, depot.getStart(), depot.getEnd());
PlanningVehicle planningVehicle = new PlanningVehicle();
planningVehicle.setId(vehicle.getId());
planningVehicle.setCapacity(vehicle.getCapacity());
// each vehicle has its deposit
planningVehicle.setDepot(timeWindowedDepot);
planningVehicles.add(planningVehicle);
});
visits.forEach(visit -> {
//submit to planner location of the visit, add to matrix for calculating distance
PlanningLocation planningLocation = optimizer.submitToPlanner(visit.getLocation());
TimeWindowedVisit timeWindowedVisit = new TimeWindowedVisit();
TimeWindowedVisit timeWindowedVisit = new TimeWindowedVisit(visit.getId(),
planningLocation, visit.getLoad(),visit.getStart(), visit.getEnd(),
visit.getServiceDuration());
planningVisits.add(timeWindowedVisit);
});
//create TWVRP
TimeWindowedVehicleRoutingSolution solution = new TimeWindowedVehicleRoutingSolution();
solution.setId(jobId);
solution.setDepotList(planningDepots);
solution.setVisitList(planningVisits);
solution.setVehicleList(planningVehicles);
return solution;
}
然后我创建求解器,开始优化并最终保存最佳结果:
public void solve(UUID jobId) {
if (!planRepository.isResolved(jobId)) {
logger.info("Starting solver");
TimeWindowedVehicleRoutingSolution solution = null;
TimeWindowedVehicleRoutingSolution timeWindowedVehicleRoutingSolution = find(jobId);
try {
SolverJob<TimeWindowedVehicleRoutingSolution, UUID> solverJob =
solverManager.solve(jobId, timeWindowedVehicleRoutingSolution);
solution = solverJob.getFinalBestSolution();
save(jobId, solution);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
} else
logger.info("This job already has a solution");
}
欢迎提供有关错误所在位置的任何帮助。我是从Optaplanner开始的,请发表任何意见。非常感谢!
对不起书法,英语不是我的语言。
答案 0 :(得分:0)
非常感谢Geoffrey,我运用了您的建议并找到了问题的根源。感谢您的帮助!
我将对发生的事情发表评论,以防它对某人有用: 碰巧我使用的是OptaWeb示例的距离计算,该示例为此目的使用了GrahHopper,默认情况下它返回最小距离,因此计算需要时间。通过引入时间窗口,我打破了分数:
Math.max(customer.getReadyTime(),
previousDepartureTime + customer.distanceFromPreviousStandstill())
我的得分很低,因为我没有对所有变量都使用相同的转换,TW:准备时间和出发时间以分钟表示,乘以1000,而距离以毫秒为单位。
示例:
距离回到我身边
因此我的分数被打破了。
我所做的是将所有时间变量都转换为毫秒:
“ HH:MM”,HH * 3,600,000和MM * 60,000
示例:
现在准备好了!您访问的每辆车的到达时间均已调整为定义的时间窗口。