Timefold带时间窗VRP:医护人员按时返家方案设计咨询
设计合理性分析与约束实现建议
一、你的修改方案整体合理,细节优化建议如下
1. 添加@NextElementShadowVariable注解的nextVisit
完全合理。在Timefold的VRP任务链中,仅靠previousVisit只能从后往前遍历任务,新增双向关联的nextVisit后,能快速定位每个任务的后续节点——尤其是要找医护的最后一个出诊任务时,直接筛选nextVisit == null的节点即可,无需遍历整个任务链。而且@NextElementShadowVariable会自动维护previousVisit和nextVisit的一致性,不用手动处理关联更新逻辑,减少出错概率。
2. 在Providers类添加下班时间字段
这个需求是必要的,但字段命名建议调整:你计划用arrivalTime,但这个名称和Visits类中代表到达出诊点的arrivalTime语义冲突,容易混淆。建议改为shiftEndTime(班次结束时间)或homeReturnDeadline(返家截止时间),语义更清晰。
3. 最后一个任务nextVisit为null + 返家时间不超下班时间
逻辑正确。最后一个出诊任务没有后续任务,所以nextVisit设为null是合理的。这里需要注意:返家时间的计算逻辑是最后任务的结束时间 + 最后任务地点到医护住所的行驶时间,这个时间必须小于等于医护的下班时间。
二、按时返家的约束实现示例
你需要添加一个硬约束(如果超时直接判定方案不可行)或软约束(超时则根据时长惩罚,允许部分超时的次优方案),以下是基于Timefold ConstraintProvider的实现示例:
public class VrpConstraintProvider implements ConstraintProvider { @Override public Constraint[] defineConstraints(ConstraintFactory constraintFactory) { return new Constraint[]{ // 硬约束:必须按时返家 returnHomeOnTimeHardConstraint(constraintFactory), // 如果允许超时,可添加软约束按超时时长惩罚 returnHomeOnTimeSoftConstraint(constraintFactory) }; } private Constraint returnHomeOnTimeHardConstraint(ConstraintFactory constraintFactory) { return constraintFactory.from(Visit.class) // 筛选最后一个出诊任务(无后续任务) .filter(visit -> visit.getNextVisit() == null) // 关联对应的医护人员 .join(Provider.class, Joiners.equal(Visit::getProvider)) // 计算返家时间:任务结束时间 + 任务地点到住所的行驶时间 .filter((visit, provider) -> { LocalDateTime returnHomeTime = visit.getEndTime().plusMinutes( calculateTravelMinutes(visit.getLocation(), provider.getHomeLocation()) ); // 返家时间超过下班时间则触发约束 return returnHomeTime.isAfter(provider.getShiftEndTime()); }) .penalize("Return home after shift end", HardSoftScore.ONE_HARD) .asConstraint(); } private Constraint returnHomeOnTimeSoftConstraint(ConstraintFactory constraintFactory) { return constraintFactory.from(Visit.class) .filter(visit -> visit.getNextVisit() == null) .join(Provider.class, Joiners.equal(Visit::getProvider)) .filter((visit, provider) -> { LocalDateTime returnHomeTime = visit.getEndTime().plusMinutes( calculateTravelMinutes(visit.getLocation(), provider.getHomeLocation()) ); return returnHomeTime.isAfter(provider.getShiftEndTime()); }) // 按超时分钟数惩罚,超时越久惩罚越重 .penalize("Return home late", HardSoftScore.ONE_SOFT, (visit, provider) -> Duration.between(provider.getShiftEndTime(), visit.getEndTime().plusMinutes(calculateTravelMinutes(visit.getLocation(), provider.getHomeLocation()))).toMinutes()) .asConstraint(); } // 这里需要你实现根据地点计算行驶时间的方法 private long calculateTravelMinutes(Location visitLocation, Location homeLocation) { // 示例:根据经纬度计算距离,再转换为行驶时间 double distance = calculateDistance(visitLocation, homeLocation); return (long) (distance / 60); // 假设时速60公里,转换为分钟 } private double calculateDistance(Location a, Location b) { // 实现实际的距离计算逻辑(比如Haversine公式) return 0; } }
约束说明
- 硬约束:如果医护返家时间超过下班时间,直接标记方案为不可行,适合必须严格遵守工作时长的场景。
- 软约束:允许超时,但根据超时时长进行惩罚,Timefold会优先选择超时时间更短的方案,适合偶尔可以弹性加班的场景。
内容的提问来源于stack exchange,提问作者Edmanan Khandelwal
相关产品推荐
相关产品推荐

