feat(javaTest): plan traffic-aware approach and service routes
This commit is contained in:
@@ -6,6 +6,12 @@ import com.ft546.wcs.demo.model.DispatchTask;
|
|||||||
import com.ft546.wcs.demo.model.Site;
|
import com.ft546.wcs.demo.model.Site;
|
||||||
import com.ft546.wcs.demo.model.VehicleSnapshot;
|
import com.ft546.wcs.demo.model.VehicleSnapshot;
|
||||||
import com.ft546.wcs.demo.traffic.GridPathPlanner;
|
import com.ft546.wcs.demo.traffic.GridPathPlanner;
|
||||||
|
import com.ft546.wcs.demo.traffic.RouteCandidatePlanner;
|
||||||
|
import com.ft546.wcs.demo.traffic.RouteCandidateSet;
|
||||||
|
import com.ft546.wcs.demo.traffic.RoutePlanDecision;
|
||||||
|
import com.ft546.wcs.demo.traffic.RoutePlanningPolicy;
|
||||||
|
import com.ft546.wcs.demo.traffic.RouteQualityEvaluator;
|
||||||
|
import com.ft546.wcs.demo.traffic.TrafficRouteSnapshot;
|
||||||
import com.ft546.wcs.shadow.application.ShadowTrafficCoordinator;
|
import com.ft546.wcs.shadow.application.ShadowTrafficCoordinator;
|
||||||
import com.ft546.wcs.shadow.decision.DecisionType;
|
import com.ft546.wcs.shadow.decision.DecisionType;
|
||||||
import com.ft546.wcs.shadow.decision.RollingDispatchPolicy;
|
import com.ft546.wcs.shadow.decision.RollingDispatchPolicy;
|
||||||
@@ -77,7 +83,8 @@ public final class HistoricalDispatchSimulator {
|
|||||||
SimulationTaskStatus.FAILED, failure.getReason(), null,
|
SimulationTaskStatus.FAILED, failure.getReason(), null,
|
||||||
Collections.emptyList(), Collections.emptyList(), 0L,
|
Collections.emptyList(), Collections.emptyList(), 0L,
|
||||||
null, null, 0, 0, 0,
|
null, null, 0, 0, 0,
|
||||||
Collections.emptyMap(), Collections.emptyMap()));
|
Collections.emptyMap(), Collections.emptyMap(),
|
||||||
|
Collections.emptyList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, SimulatedVehicleRuntime> vehicles = createRuntimes(input);
|
Map<String, SimulatedVehicleRuntime> vehicles = createRuntimes(input);
|
||||||
@@ -91,6 +98,8 @@ public final class HistoricalDispatchSimulator {
|
|||||||
SimulationArchive archive = new SimulationArchive();
|
SimulationArchive archive = new SimulationArchive();
|
||||||
IdempotentWmsCompletionReceiver wms = new IdempotentWmsCompletionReceiver();
|
IdempotentWmsCompletionReceiver wms = new IdempotentWmsCompletionReceiver();
|
||||||
GridPathPlanner planner = new GridPathPlanner();
|
GridPathPlanner planner = new GridPathPlanner();
|
||||||
|
RouteCandidatePlanner routePlanner = new RouteCandidatePlanner();
|
||||||
|
RouteQualityEvaluator routeEvaluator = new RouteQualityEvaluator();
|
||||||
VehicleAssignmentService assignment = new VehicleAssignmentService();
|
VehicleAssignmentService assignment = new VehicleAssignmentService();
|
||||||
CorridorReservationManager reservations = new CorridorReservationManager();
|
CorridorReservationManager reservations = new CorridorReservationManager();
|
||||||
ClearanceMovePlanner clearancePlanner = new ClearanceMovePlanner();
|
ClearanceMovePlanner clearancePlanner = new ClearanceMovePlanner();
|
||||||
@@ -105,8 +114,8 @@ public final class HistoricalDispatchSimulator {
|
|||||||
applyVehicleEvents(tick, now, faults, vehicles, active,
|
applyVehicleEvents(tick, now, faults, vehicles, active,
|
||||||
clearanceMoves, queue, executions, reservations, events);
|
clearanceMoves, queue, executions, reservations, events);
|
||||||
assignReadyTasks(tick, now, input, queue, vehicles, active,
|
assignReadyTasks(tick, now, input, queue, vehicles, active,
|
||||||
clearanceMoves, executions, terminal, planner, assignment, plc,
|
clearanceMoves, executions, terminal, routePlanner, routeEvaluator,
|
||||||
reservations, events);
|
assignment, plc, ledger, reservations, events);
|
||||||
// 本 tick 已通过的边被共享给两类移动,禁止另一车辆同 tick 重复或反向穿越。
|
// 本 tick 已通过的边被共享给两类移动,禁止另一车辆同 tick 重复或反向穿越。
|
||||||
Set<String> traversedEdges = new LinkedHashSet<String>();
|
Set<String> traversedEdges = new LinkedHashSet<String>();
|
||||||
ClearanceProgress clearanceProgress = advanceClearanceMoves(
|
ClearanceProgress clearanceProgress = advanceClearanceMoves(
|
||||||
@@ -269,9 +278,11 @@ public final class HistoricalDispatchSimulator {
|
|||||||
* @param clearanceMoves 执行该处理所需的 clearance moves 输入。
|
* @param clearanceMoves 执行该处理所需的 clearance moves 输入。
|
||||||
* @param executions 执行该处理所需的 executions 输入。
|
* @param executions 执行该处理所需的 executions 输入。
|
||||||
* @param terminal 执行该处理所需的 terminal 输入。
|
* @param terminal 执行该处理所需的 terminal 输入。
|
||||||
* @param planner 执行该处理所需的 planner 输入。
|
* @param routePlanner 生成 110% 物理距离内的确定性无环候选。
|
||||||
|
* @param routeEvaluator 使用同一交通快照按固定业务元组选择候选。
|
||||||
* @param assignment 执行该处理所需的 assignment 输入。
|
* @param assignment 执行该处理所需的 assignment 输入。
|
||||||
* @param plc 执行该处理所需的 plc 输入。
|
* @param plc 执行该处理所需的 plc 输入。
|
||||||
|
* @param ledger 当前节点和边资源的权威台账。
|
||||||
* @param reservations 执行该处理所需的 reservations 输入。
|
* @param reservations 执行该处理所需的 reservations 输入。
|
||||||
* @param events 执行该处理所需的 events 输入。
|
* @param events 执行该处理所需的 events 输入。
|
||||||
* @return 本 tick 是否至少有一个任务被分配或因永久不可达关闭。
|
* @return 本 tick 是否至少有一个任务被分配或因永久不可达关闭。
|
||||||
@@ -283,9 +294,11 @@ public final class HistoricalDispatchSimulator {
|
|||||||
Map<String, ClearanceMove> clearanceMoves,
|
Map<String, ClearanceMove> clearanceMoves,
|
||||||
Map<String, Execution> executions,
|
Map<String, Execution> executions,
|
||||||
Map<String, SimulationTaskResult> terminal,
|
Map<String, SimulationTaskResult> terminal,
|
||||||
GridPathPlanner planner,
|
RouteCandidatePlanner routePlanner,
|
||||||
|
RouteQualityEvaluator routeEvaluator,
|
||||||
VehicleAssignmentService assignment,
|
VehicleAssignmentService assignment,
|
||||||
SimulatedPlcGateway plc,
|
SimulatedPlcGateway plc,
|
||||||
|
ResourceLedger ledger,
|
||||||
CorridorReservationManager reservations,
|
CorridorReservationManager reservations,
|
||||||
List<SimulationTickEvent> events) {
|
List<SimulationTickEvent> events) {
|
||||||
boolean progress = false;
|
boolean progress = false;
|
||||||
@@ -295,10 +308,11 @@ public final class HistoricalDispatchSimulator {
|
|||||||
for (HistoricalSimulationTask task : ready) {
|
for (HistoricalSimulationTask task : ready) {
|
||||||
if (terminal.containsKey(task.getSimulationTaskId())) continue;
|
if (terminal.containsKey(task.getSimulationTaskId())) continue;
|
||||||
|
|
||||||
// 服务路径由完整地图重新计算。历史 path_info 不参与规划,空路径表示业务端点永久不可达。
|
// 先生成服务段物理候选。这里只使用永久地图,临时占用不能把任务误关为 NO_ROUTE。
|
||||||
List<String> servicePath = planner.shortestPath(input.getGridMap(),
|
RouteCandidateSet serviceCandidates = routePlanner.plan(input.getGridMap(),
|
||||||
task.getPhysicalSource(), task.getPhysicalTarget(), Collections.emptySet());
|
task.getPhysicalSource(), task.getPhysicalTarget(),
|
||||||
if (servicePath.isEmpty()) {
|
Collections.<String>emptySet(), RoutePlanningPolicy.defaultPolicy());
|
||||||
|
if (serviceCandidates.getCandidates().isEmpty()) {
|
||||||
queue.remove(task.getSimulationTaskId());
|
queue.remove(task.getSimulationTaskId());
|
||||||
terminal.put(task.getSimulationTaskId(), result(executions.get(
|
terminal.put(task.getSimulationTaskId(), result(executions.get(
|
||||||
task.getSimulationTaskId()), SimulationTaskStatus.FAILED,
|
task.getSimulationTaskId()), SimulationTaskStatus.FAILED,
|
||||||
@@ -308,22 +322,24 @@ public final class HistoricalDispatchSimulator {
|
|||||||
|
|
||||||
// 为每台空闲且健康的车计算到任务源点的接近路径,无法接近源点的车不进入本轮选车。
|
// 为每台空闲且健康的车计算到任务源点的接近路径,无法接近源点的车不进入本轮选车。
|
||||||
List<VehicleSnapshot> candidates = new ArrayList<VehicleSnapshot>();
|
List<VehicleSnapshot> candidates = new ArrayList<VehicleSnapshot>();
|
||||||
Map<String, List<String>> approaches = new LinkedHashMap<String, List<String>>();
|
Map<String, RouteCandidateSet> approaches =
|
||||||
|
new LinkedHashMap<String, RouteCandidateSet>();
|
||||||
for (SimulatedVehicleRuntime vehicle : vehicles.values()) {
|
for (SimulatedVehicleRuntime vehicle : vehicles.values()) {
|
||||||
if (!vehicle.isAssignmentEligible() || active.containsKey(vehicle.getVehicleId())
|
if (!vehicle.isAssignmentEligible() || active.containsKey(vehicle.getVehicleId())
|
||||||
|| clearanceMoves.containsKey(vehicle.getVehicleId())) {
|
|| clearanceMoves.containsKey(vehicle.getVehicleId())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
List<String> approach = planner.shortestPath(input.getGridMap(),
|
RouteCandidateSet approach = routePlanner.plan(input.getGridMap(),
|
||||||
vehicle.getCurrentSite(), task.getPhysicalSource(), Collections.emptySet());
|
vehicle.getCurrentSite(), task.getPhysicalSource(),
|
||||||
if (!approach.isEmpty()) {
|
Collections.<String>emptySet(), RoutePlanningPolicy.defaultPolicy());
|
||||||
|
if (!approach.getCandidates().isEmpty()) {
|
||||||
candidates.add(vehicle.snapshot());
|
candidates.add(vehicle.snapshot());
|
||||||
approaches.put(vehicle.getVehicleId(), approach);
|
approaches.put(vehicle.getVehicleId(), approach);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (candidates.isEmpty()) {
|
if (candidates.isEmpty()) {
|
||||||
// 暂无可分配车不等于失败;只有全车队都无法抵达源点时才关闭为 NO_ROUTE。
|
// 暂无可分配车不等于失败;只有全车队都无法抵达源点时才关闭为 NO_ROUTE。
|
||||||
if (!fleetCanReach(task, vehicles, input, planner)) {
|
if (!fleetCanReach(task, vehicles, input, routePlanner)) {
|
||||||
queue.remove(task.getSimulationTaskId());
|
queue.remove(task.getSimulationTaskId());
|
||||||
terminal.put(task.getSimulationTaskId(), result(executions.get(
|
terminal.put(task.getSimulationTaskId(), result(executions.get(
|
||||||
task.getSimulationTaskId()), SimulationTaskStatus.FAILED,
|
task.getSimulationTaskId()), SimulationTaskStatus.FAILED,
|
||||||
@@ -343,8 +359,17 @@ public final class HistoricalDispatchSimulator {
|
|||||||
if (!decision.getVehicle().isPresent()) continue;
|
if (!decision.getVehicle().isPresent()) continue;
|
||||||
String vehicleId = decision.getVehicle().get().getVehicleId();
|
String vehicleId = decision.getVehicle().get().getVehicleId();
|
||||||
|
|
||||||
// 车辆先走到取货/起始点,再沿新规划的服务路径到达任务终点,形成实际执行路线。
|
// 所有候选共用这一刻的占点、边预约、走廊方向和活动路线,保证比较口径一致。
|
||||||
List<String> route = new ArrayList<String>(approaches.get(vehicleId));
|
TrafficRouteSnapshot traffic = trafficSnapshot(
|
||||||
|
vehicles, active, ledger, reservations);
|
||||||
|
RoutePlanDecision approachDecision = routeEvaluator.select(input.getGridMap(),
|
||||||
|
approaches.get(vehicleId), traffic, task.getSimulationTaskId(), vehicleId);
|
||||||
|
RoutePlanDecision serviceDecision = routeEvaluator.select(input.getGridMap(),
|
||||||
|
serviceCandidates, traffic, task.getSimulationTaskId(), vehicleId);
|
||||||
|
|
||||||
|
// 车辆先走到业务源点,再执行服务段;拼接时去掉重复源点。
|
||||||
|
List<String> route = new ArrayList<String>(approachDecision.getSelectedPath());
|
||||||
|
List<String> servicePath = serviceDecision.getSelectedPath();
|
||||||
for (int i = 1; i < servicePath.size(); i++) route.add(servicePath.get(i));
|
for (int i = 1; i < servicePath.size(); i++) route.add(servicePath.get(i));
|
||||||
Execution execution = executions.get(task.getSimulationTaskId());
|
Execution execution = executions.get(task.getSimulationTaskId());
|
||||||
|
|
||||||
@@ -365,6 +390,13 @@ public final class HistoricalDispatchSimulator {
|
|||||||
execution.lastVehicleId = vehicleId;
|
execution.lastVehicleId = vehicleId;
|
||||||
execution.plannedPath = new ArrayList<String>(servicePath);
|
execution.plannedPath = new ArrayList<String>(servicePath);
|
||||||
execution.assignedVehicles.add(vehicleId);
|
execution.assignedVehicles.add(vehicleId);
|
||||||
|
execution.pathVersion++;
|
||||||
|
execution.planningEvidence.add(new RoutePlanningEvidence(
|
||||||
|
RoutePlanningEvidence.PlanningStage.APPROACH,
|
||||||
|
execution.pathVersion, now, false, approachDecision));
|
||||||
|
execution.planningEvidence.add(new RoutePlanningEvidence(
|
||||||
|
RoutePlanningEvidence.PlanningStage.SERVICE,
|
||||||
|
execution.pathVersion, now, false, serviceDecision));
|
||||||
active.put(vehicleId, new ActiveTask(execution, route));
|
active.put(vehicleId, new ActiveTask(execution, route));
|
||||||
queue.remove(task.getSimulationTaskId());
|
queue.remove(task.getSimulationTaskId());
|
||||||
events.add(event(tick, now, "ASSIGN", task.getSimulationTaskId(), vehicleId,
|
events.add(event(tick, now, "ASSIGN", task.getSimulationTaskId(), vehicleId,
|
||||||
@@ -387,14 +419,52 @@ public final class HistoricalDispatchSimulator {
|
|||||||
private boolean fleetCanReach(HistoricalSimulationTask task,
|
private boolean fleetCanReach(HistoricalSimulationTask task,
|
||||||
Map<String, SimulatedVehicleRuntime> vehicles,
|
Map<String, SimulatedVehicleRuntime> vehicles,
|
||||||
HistoricalSimulationInput input,
|
HistoricalSimulationInput input,
|
||||||
GridPathPlanner planner) {
|
RouteCandidatePlanner planner) {
|
||||||
for (SimulatedVehicleRuntime vehicle : vehicles.values()) {
|
for (SimulatedVehicleRuntime vehicle : vehicles.values()) {
|
||||||
if (!planner.shortestPath(input.getGridMap(), vehicle.getCurrentSite(),
|
if (!planner.plan(input.getGridMap(), vehicle.getCurrentSite(),
|
||||||
task.getPhysicalSource(), Collections.emptySet()).isEmpty()) return true;
|
task.getPhysicalSource(), Collections.<String>emptySet(),
|
||||||
|
RoutePlanningPolicy.defaultPolicy()).getCandidates().isEmpty()) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冻结本次规划看到的车辆占点、已预约方向、权威边资源和其他活动任务剩余路线。
|
||||||
|
*/
|
||||||
|
private TrafficRouteSnapshot trafficSnapshot(
|
||||||
|
Map<String, SimulatedVehicleRuntime> vehicles,
|
||||||
|
Map<String, ActiveTask> active,
|
||||||
|
ResourceLedger ledger,
|
||||||
|
CorridorReservationManager reservations) {
|
||||||
|
TrafficRouteSnapshot.Builder snapshot = TrafficRouteSnapshot.builder();
|
||||||
|
for (SimulatedVehicleRuntime vehicle : vehicles.values()) {
|
||||||
|
snapshot.occupyNode(vehicle.getCurrentSite(), vehicle.getVehicleId());
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, ActiveTask> entry : active.entrySet()) {
|
||||||
|
ActiveTask task = entry.getValue();
|
||||||
|
List<String> remaining = task.route.subList(task.index, task.route.size());
|
||||||
|
snapshot.activeRoute(task.execution.task.getSimulationTaskId(), remaining);
|
||||||
|
|
||||||
|
// 只有台账中确实由该活动车辆持有的边才属于当前瞬时独占资源。
|
||||||
|
for (int index = 1; index < remaining.size(); index++) {
|
||||||
|
String from = remaining.get(index - 1);
|
||||||
|
String to = remaining.get(index);
|
||||||
|
if (ledger.holdersFor(ResourceKey.edge(from, to)).contains(entry.getKey())) {
|
||||||
|
snapshot.reserveEdge(from, to,
|
||||||
|
task.execution.task.getSimulationTaskId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (CorridorReservationManager.CorridorReservationSnapshot reservation
|
||||||
|
: reservations.snapshot()) {
|
||||||
|
for (String taskId : reservation.getTaskIds()) {
|
||||||
|
snapshot.reserveDirection(reservation.getFromSite(),
|
||||||
|
reservation.getToSite(), taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return snapshot.build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 推进全部已分配任务的下一条边,并在移动前依次执行方向冲突、清障、交通决策、资源台账和 PLC 确认。
|
* 推进全部已分配任务的下一条边,并在移动前依次执行方向冲突、清障、交通决策、资源台账和 PLC 确认。
|
||||||
*
|
*
|
||||||
@@ -920,7 +990,8 @@ public final class HistoricalDispatchSimulator {
|
|||||||
status == SimulationTaskStatus.COMPLETED ? now : null,
|
status == SimulationTaskStatus.COMPLETED ? now : null,
|
||||||
execution.holdCount,
|
execution.holdCount,
|
||||||
plc.attemptsFor(execution.task.getSimulationTaskId()),
|
plc.attemptsFor(execution.task.getSimulationTaskId()),
|
||||||
execution.retryCount, execution.holdReasons, execution.retryReasons);
|
execution.retryCount, execution.holdReasons, execution.retryReasons,
|
||||||
|
execution.planningEvidence);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1004,6 +1075,9 @@ public final class HistoricalDispatchSimulator {
|
|||||||
new LinkedHashMap<String, Integer>();
|
new LinkedHashMap<String, Integer>();
|
||||||
private final Map<String, Integer> retryReasons =
|
private final Map<String, Integer> retryReasons =
|
||||||
new LinkedHashMap<String, Integer>();
|
new LinkedHashMap<String, Integer>();
|
||||||
|
private final List<RoutePlanningEvidence> planningEvidence =
|
||||||
|
new ArrayList<RoutePlanningEvidence>();
|
||||||
|
private int pathVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建 {@code Execution} 实例并初始化其内部状态。
|
* 创建 {@code Execution} 实例并初始化其内部状态。
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.ft546.wcs.shadow.history.simulation;
|
||||||
|
|
||||||
|
import com.ft546.wcs.demo.traffic.RoutePlanDecision;
|
||||||
|
|
||||||
|
/** 保存一次接近段或服务段路线选择的不可变业务证据。 */
|
||||||
|
public final class RoutePlanningEvidence {
|
||||||
|
public enum PlanningStage { APPROACH, SERVICE }
|
||||||
|
|
||||||
|
private final PlanningStage stage;
|
||||||
|
private final int pathVersion;
|
||||||
|
private final long simulationMillis;
|
||||||
|
private final boolean reroute;
|
||||||
|
private final RoutePlanDecision decision;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录一次已实际采用的中心规划决定。
|
||||||
|
*
|
||||||
|
* @param stage 车辆到源点的接近段或源点到目标的服务段
|
||||||
|
* @param pathVersion 任务执行路线版本,从 1 开始递增
|
||||||
|
* @param simulationMillis 作出决定的仿真时间
|
||||||
|
* @param reroute 是否由连续交通 HOLD 触发
|
||||||
|
* @param decision 候选集合、质量元组和最终选择
|
||||||
|
*/
|
||||||
|
public RoutePlanningEvidence(PlanningStage stage, int pathVersion,
|
||||||
|
long simulationMillis, boolean reroute,
|
||||||
|
RoutePlanDecision decision) {
|
||||||
|
if (stage == null || pathVersion <= 0 || simulationMillis < 0L || decision == null) {
|
||||||
|
throw new IllegalArgumentException("valid route planning evidence is required");
|
||||||
|
}
|
||||||
|
this.stage = stage;
|
||||||
|
this.pathVersion = pathVersion;
|
||||||
|
this.simulationMillis = simulationMillis;
|
||||||
|
this.reroute = reroute;
|
||||||
|
this.decision = decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 返回本次决定对应接近段还是服务段。 */
|
||||||
|
public PlanningStage getStage() { return stage; }
|
||||||
|
|
||||||
|
/** 返回采用该决定后的完整执行路线版本。 */
|
||||||
|
public int getPathVersion() { return pathVersion; }
|
||||||
|
|
||||||
|
/** 返回本次决定发生的仿真时间。 */
|
||||||
|
public long getSimulationMillis() { return simulationMillis; }
|
||||||
|
|
||||||
|
/** 返回该决定是否来自运行中的交通重规划。 */
|
||||||
|
public boolean isReroute() { return reroute; }
|
||||||
|
|
||||||
|
/** 返回候选、质量和选择原因。 */
|
||||||
|
public RoutePlanDecision getDecision() { return decision; }
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ public final class SimulationTaskResult {
|
|||||||
private final int retryCount;
|
private final int retryCount;
|
||||||
private final Map<String, Integer> holdReasons;
|
private final Map<String, Integer> holdReasons;
|
||||||
private final Map<String, Integer> retryReasons;
|
private final Map<String, Integer> retryReasons;
|
||||||
|
private final List<RoutePlanningEvidence> planningEvidence;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建 {@code SimulationTaskResult} 实例并初始化其内部状态。
|
* 创建 {@code SimulationTaskResult} 实例并初始化其内部状态。
|
||||||
@@ -44,6 +45,7 @@ public final class SimulationTaskResult {
|
|||||||
* @param retryCount 执行该处理所需的 retry count 输入。
|
* @param retryCount 执行该处理所需的 retry count 输入。
|
||||||
* @param holdReasons 执行该处理所需的 hold reasons 输入。
|
* @param holdReasons 执行该处理所需的 hold reasons 输入。
|
||||||
* @param retryReasons 执行该处理所需的 retry reasons 输入。
|
* @param retryReasons 执行该处理所需的 retry reasons 输入。
|
||||||
|
* @param planningEvidence 每个实际采用路径版本的接近段和服务段决策证据。
|
||||||
*/
|
*/
|
||||||
SimulationTaskResult(String simulationTaskId, HistoricalQueueRecord record,
|
SimulationTaskResult(String simulationTaskId, HistoricalQueueRecord record,
|
||||||
SimulationTaskStatus status, String reason,
|
SimulationTaskStatus status, String reason,
|
||||||
@@ -52,7 +54,8 @@ public final class SimulationTaskResult {
|
|||||||
Long firstAssignedMillis, Long completedMillis,
|
Long firstAssignedMillis, Long completedMillis,
|
||||||
int holdCount, int protocolAttempts, int retryCount,
|
int holdCount, int protocolAttempts, int retryCount,
|
||||||
Map<String, Integer> holdReasons,
|
Map<String, Integer> holdReasons,
|
||||||
Map<String, Integer> retryReasons) {
|
Map<String, Integer> retryReasons,
|
||||||
|
List<RoutePlanningEvidence> planningEvidence) {
|
||||||
this.simulationTaskId = simulationTaskId;
|
this.simulationTaskId = simulationTaskId;
|
||||||
this.record = record;
|
this.record = record;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
@@ -68,6 +71,7 @@ public final class SimulationTaskResult {
|
|||||||
this.retryCount = retryCount;
|
this.retryCount = retryCount;
|
||||||
this.holdReasons = immutableMap(holdReasons);
|
this.holdReasons = immutableMap(holdReasons);
|
||||||
this.retryReasons = immutableMap(retryReasons);
|
this.retryReasons = immutableMap(retryReasons);
|
||||||
|
this.planningEvidence = immutable(planningEvidence);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -175,6 +179,9 @@ public final class SimulationTaskResult {
|
|||||||
* @return 本次处理产生的结果;调用方据此继续后续流程。
|
* @return 本次处理产生的结果;调用方据此继续后续流程。
|
||||||
*/
|
*/
|
||||||
public Map<String, Integer> getRetryReasons() { return retryReasons; }
|
public Map<String, Integer> getRetryReasons() { return retryReasons; }
|
||||||
|
|
||||||
|
/** 返回任务每个路径版本的接近段和服务段中心规划证据。 */
|
||||||
|
public List<RoutePlanningEvidence> getPlanningEvidence() { return planningEvidence; }
|
||||||
/**
|
/**
|
||||||
* 返回当前对象保存的“waiting millis”信息。
|
* 返回当前对象保存的“waiting millis”信息。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -78,11 +78,34 @@ class HistoricalDispatchSimulatorTest {
|
|||||||
alternateRouteInput(), SimulationConfig.defaults(),
|
alternateRouteInput(), SimulationConfig.defaults(),
|
||||||
DeterministicFaultPlan.none());
|
DeterministicFaultPlan.none());
|
||||||
|
|
||||||
assertEquals(Arrays.asList("A", "C"),
|
assertEquals(Arrays.asList("A", "B", "C"),
|
||||||
result.getTaskResults().get(0).getPlannedPath());
|
result.getTaskResults().get(0).getPlannedPath());
|
||||||
assertEquals(1, result.getReplannedCount());
|
assertEquals(1, result.getReplannedCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void selectsBoundedConflictFreeServiceRouteAndStoresBothPlanningStages() {
|
||||||
|
HistoricalDispatchSimulationResult result = new HistoricalDispatchSimulator().simulate(
|
||||||
|
conflictAwareRoutingInput(), new SimulationConfig(100, 30_000L, 3, 100, 300),
|
||||||
|
DeterministicFaultPlan.none());
|
||||||
|
|
||||||
|
SimulationTaskResult second = result.getTaskResults().stream()
|
||||||
|
.filter(task -> "SAFE-2".equals(task.getSimulationTaskId()))
|
||||||
|
.findFirst().orElseThrow(AssertionError::new);
|
||||||
|
assertEquals(Arrays.asList("D", "C", "A"), second.getPlannedPath());
|
||||||
|
assertEquals(2, second.getPlanningEvidence().size());
|
||||||
|
RoutePlanningEvidence approach = second.getPlanningEvidence().get(0);
|
||||||
|
RoutePlanningEvidence service = second.getPlanningEvidence().get(1);
|
||||||
|
assertEquals(RoutePlanningEvidence.PlanningStage.APPROACH, approach.getStage());
|
||||||
|
assertEquals(RoutePlanningEvidence.PlanningStage.SERVICE, service.getStage());
|
||||||
|
assertEquals(1, service.getPathVersion());
|
||||||
|
assertEquals("FEWER_OPPOSITE_DIRECTION_CONFLICTS",
|
||||||
|
service.getDecision().getDecisionReason());
|
||||||
|
assertTrue(service.getDecision().getDetourRatio() <= 1.10d);
|
||||||
|
assertEquals(2, service.getDecision().getCandidateSet().getCandidates().size());
|
||||||
|
assertEquals(0, result.getFailedCount());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证“moves idle blockers node by node and completes both business directions”场景,确保相关业务规则和断言成立。
|
* 验证“moves idle blockers node by node and completes both business directions”场景,确保相关业务规则和断言成立。
|
||||||
*
|
*
|
||||||
@@ -211,13 +234,46 @@ class HistoricalDispatchSimulatorTest {
|
|||||||
"test", "test", nodes, edges, Collections.emptyMap(),
|
"test", "test", nodes, edges, Collections.emptyMap(),
|
||||||
Collections.emptySet(), Collections.emptySet()));
|
Collections.emptySet(), Collections.emptySet()));
|
||||||
HistoricalSimulationTask task = customTask(
|
HistoricalSimulationTask task = customTask(
|
||||||
"R1", "A", "C", 1, 0, Arrays.asList("A", "B", "C"));
|
"R1", "A", "C", 1, 0, Arrays.asList("A", "C"));
|
||||||
return new HistoricalSimulationInput(Collections.singletonList(task),
|
return new HistoricalSimulationInput(Collections.singletonList(task),
|
||||||
Collections.emptyList(), grid, traffic,
|
Collections.emptyList(), grid, traffic,
|
||||||
Collections.singletonList(new VehicleSnapshot(
|
Collections.singletonList(new VehicleSnapshot(
|
||||||
"1", 1, "A", 5, 1, 100, true, true, false)));
|
"1", 1, "A", 5, 1, 100, true, true, false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HistoricalSimulationInput conflictAwareRoutingInput() {
|
||||||
|
GridMap grid = new GridMap();
|
||||||
|
grid.addSite(new Site("A", 1, 0, 0, "TEST"));
|
||||||
|
grid.addSite(new Site("B", 1, 5, 0, "TEST"));
|
||||||
|
grid.addSite(new Site("C", 1, 5, 1, "TEST"));
|
||||||
|
grid.addSite(new Site("D", 1, 10, 0, "TEST"));
|
||||||
|
grid.connect("A", "B");
|
||||||
|
grid.connect("B", "D");
|
||||||
|
grid.connect("A", "C");
|
||||||
|
grid.connect("C", "D");
|
||||||
|
|
||||||
|
LinkedHashSet<String> nodes = new LinkedHashSet<String>(
|
||||||
|
Arrays.asList("A", "B", "C", "D"));
|
||||||
|
LinkedHashSet<TrafficMapCompiler.EdgeDefinition> edges =
|
||||||
|
new LinkedHashSet<TrafficMapCompiler.EdgeDefinition>();
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("A", "B"));
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("B", "D"));
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("A", "C"));
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("C", "D"));
|
||||||
|
EffectiveTrafficMap traffic = new TrafficMapCompiler().compile(
|
||||||
|
new TrafficMapCompiler.MapDefinition("CONFLICT-AWARE", 1L, "TEST", true,
|
||||||
|
"test", "test", nodes, edges, Collections.emptyMap(),
|
||||||
|
Collections.emptySet(), Collections.emptySet()));
|
||||||
|
List<HistoricalSimulationTask> tasks = Arrays.asList(
|
||||||
|
customTask("SAFE-1", "A", "D", 1, 0, Arrays.asList("A", "B", "D")),
|
||||||
|
customTask("SAFE-2", "D", "A", 2, 0, Arrays.asList("D", "B", "A")));
|
||||||
|
List<VehicleSnapshot> vehicles = Arrays.asList(
|
||||||
|
new VehicleSnapshot("1", 1, "A", 5, 1, 100, true, true, false),
|
||||||
|
new VehicleSnapshot("2", 1, "D", 5, 1, 100, true, true, false));
|
||||||
|
return new HistoricalSimulationInput(tasks, Collections.emptyList(), grid,
|
||||||
|
traffic, vehicles);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证“blocked three vehicle corridor input”场景,确保相关业务规则和断言成立。
|
* 验证“blocked three vehicle corridor input”场景,确保相关业务规则和断言成立。
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user