feat(javaTest): clear blocked routes with real vehicle moves
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
package com.ft546.wcs.shadow.history.simulation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class ClearanceMove {
|
||||||
|
private final String vehicleId;
|
||||||
|
private final String ownerTaskId;
|
||||||
|
private final List<String> route;
|
||||||
|
private int index;
|
||||||
|
private int protocolAttempts;
|
||||||
|
|
||||||
|
ClearanceMove(String vehicleId, String ownerTaskId, List<String> route) {
|
||||||
|
this.vehicleId = vehicleId;
|
||||||
|
this.ownerTaskId = ownerTaskId;
|
||||||
|
this.route = Collections.unmodifiableList(new ArrayList<String>(route));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVehicleId() { return vehicleId; }
|
||||||
|
public String getOwnerTaskId() { return ownerTaskId; }
|
||||||
|
public String getSourceSite() { return route.get(0); }
|
||||||
|
public String getTargetSite() { return route.get(route.size() - 1); }
|
||||||
|
public List<String> getRoute() { return route; }
|
||||||
|
public int getIndex() { return index; }
|
||||||
|
public int getProtocolAttempts() { return protocolAttempts; }
|
||||||
|
public boolean isComplete() { return index >= route.size() - 1; }
|
||||||
|
|
||||||
|
String currentSite() { return route.get(index); }
|
||||||
|
String nextSite() { return route.get(index + 1); }
|
||||||
|
void moved() { index++; }
|
||||||
|
void attemptedProtocol() { protocolAttempts++; }
|
||||||
|
String clearanceTaskId() { return "CLEARANCE:" + ownerTaskId + ":" + vehicleId; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.ft546.wcs.shadow.history.simulation;
|
||||||
|
|
||||||
|
import com.ft546.wcs.demo.traffic.GridMap;
|
||||||
|
import com.ft546.wcs.demo.traffic.GridPathPlanner;
|
||||||
|
import com.ft546.wcs.shadow.resource.ResourceKey;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class ClearanceMovePlanner {
|
||||||
|
public Optional<ClearanceMove> plan(String vehicleId, String currentSite,
|
||||||
|
String ownerTaskId, List<String> ownerRemainingRoute,
|
||||||
|
Set<String> occupiedSites,
|
||||||
|
Set<ResourceKey> reservedResources,
|
||||||
|
GridMap map) {
|
||||||
|
if (vehicleId == null || currentSite == null || ownerTaskId == null
|
||||||
|
|| ownerRemainingRoute == null || occupiedSites == null
|
||||||
|
|| reservedResources == null || map == null) {
|
||||||
|
throw new IllegalArgumentException("clearance planning inputs are required");
|
||||||
|
}
|
||||||
|
Set<String> excluded = new LinkedHashSet<String>(ownerRemainingRoute);
|
||||||
|
excluded.addAll(occupiedSites);
|
||||||
|
for (ResourceKey resource : reservedResources) {
|
||||||
|
if (resource.value().startsWith("NODE:")) {
|
||||||
|
excluded.add(resource.value().substring("NODE:".length()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
excluded.remove(currentSite);
|
||||||
|
|
||||||
|
GridPathPlanner planner = new GridPathPlanner();
|
||||||
|
List<Candidate> candidates = new ArrayList<Candidate>();
|
||||||
|
List<String> siteIds = new ArrayList<String>(map.getSites().keySet());
|
||||||
|
Collections.sort(siteIds);
|
||||||
|
for (String target : siteIds) {
|
||||||
|
if (target.equals(currentSite) || excluded.contains(target)) continue;
|
||||||
|
List<String> route = planner.shortestPath(map, currentSite, target, excluded);
|
||||||
|
if (!route.isEmpty()) {
|
||||||
|
candidates.add(new Candidate(target, route,
|
||||||
|
map.getNeighbors(target).size() <= 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(candidates, Comparator
|
||||||
|
.comparingInt((Candidate candidate) -> candidate.route.size())
|
||||||
|
.thenComparing(candidate -> candidate.branch ? 0 : 1)
|
||||||
|
.thenComparing(candidate -> candidate.target));
|
||||||
|
if (candidates.isEmpty()) return Optional.empty();
|
||||||
|
return Optional.of(new ClearanceMove(vehicleId, ownerTaskId,
|
||||||
|
candidates.get(0).route));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Candidate {
|
||||||
|
private final String target;
|
||||||
|
private final List<String> route;
|
||||||
|
private final boolean branch;
|
||||||
|
|
||||||
|
private Candidate(String target, List<String> route, boolean branch) {
|
||||||
|
this.target = target;
|
||||||
|
this.route = route;
|
||||||
|
this.branch = branch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,7 @@ public final class HistoricalDispatchSimulator {
|
|||||||
|
|
||||||
Map<String, SimulatedVehicleRuntime> vehicles = createRuntimes(input);
|
Map<String, SimulatedVehicleRuntime> vehicles = createRuntimes(input);
|
||||||
Map<String, ActiveTask> active = new LinkedHashMap<String, ActiveTask>();
|
Map<String, ActiveTask> active = new LinkedHashMap<String, ActiveTask>();
|
||||||
|
Map<String, ClearanceMove> clearanceMoves = new LinkedHashMap<String, ClearanceMove>();
|
||||||
ResourceLedger ledger = new ResourceLedger(input.getTrafficMap().getCapacities());
|
ResourceLedger ledger = new ResourceLedger(input.getTrafficMap().getCapacities());
|
||||||
SimulationInvariantSummary invariants = new SimulationInvariantSummary();
|
SimulationInvariantSummary invariants = new SimulationInvariantSummary();
|
||||||
occupyInitialNodes(vehicles, ledger, invariants);
|
occupyInitialNodes(vehicles, ledger, invariants);
|
||||||
@@ -75,6 +76,7 @@ public final class HistoricalDispatchSimulator {
|
|||||||
GridPathPlanner planner = new GridPathPlanner();
|
GridPathPlanner planner = new GridPathPlanner();
|
||||||
VehicleAssignmentService assignment = new VehicleAssignmentService();
|
VehicleAssignmentService assignment = new VehicleAssignmentService();
|
||||||
CorridorReservationManager reservations = new CorridorReservationManager();
|
CorridorReservationManager reservations = new CorridorReservationManager();
|
||||||
|
ClearanceMovePlanner clearancePlanner = new ClearanceMovePlanner();
|
||||||
|
|
||||||
long now = 0L;
|
long now = 0L;
|
||||||
long tick = 0L;
|
long tick = 0L;
|
||||||
@@ -83,13 +85,17 @@ public final class HistoricalDispatchSimulator {
|
|||||||
config.getNoProgressLimit(), terminal.size());
|
config.getNoProgressLimit(), terminal.size());
|
||||||
while (terminal.size() < expected && tick < config.getTickLimit()) {
|
while (terminal.size() < expected && tick < config.getTickLimit()) {
|
||||||
applyVehicleEvents(tick, now, faults, vehicles, active,
|
applyVehicleEvents(tick, now, faults, vehicles, active,
|
||||||
queue, executions, reservations, events);
|
clearanceMoves, queue, executions, reservations, events);
|
||||||
assignReadyTasks(tick, now, input, queue, vehicles, active,
|
assignReadyTasks(tick, now, input, queue, vehicles, active,
|
||||||
executions, terminal, planner, assignment, plc, reservations, events);
|
clearanceMoves, executions, terminal, planner, assignment, plc,
|
||||||
|
reservations, events);
|
||||||
Set<String> traversedEdges = new LinkedHashSet<String>();
|
Set<String> traversedEdges = new LinkedHashSet<String>();
|
||||||
|
advanceClearanceMoves(tick, now, input, config, vehicles, clearanceMoves,
|
||||||
|
ledger, plc, events, traversedEdges);
|
||||||
advanceActiveTasks(tick, now, input, config, queue, vehicles,
|
advanceActiveTasks(tick, now, input, config, queue, vehicles,
|
||||||
active, executions, terminal, planner, ledger, plc, archive, wms,
|
active, executions, terminal, planner, ledger, plc, archive, wms,
|
||||||
invariants, reservations, events, traversedEdges);
|
invariants, reservations, clearanceMoves, clearancePlanner,
|
||||||
|
events, traversedEdges);
|
||||||
verifyLedger(ledger, invariants);
|
verifyLedger(ledger, invariants);
|
||||||
|
|
||||||
if (watchdog.observe(terminal.size())) {
|
if (watchdog.observe(terminal.size())) {
|
||||||
@@ -138,6 +144,7 @@ public final class HistoricalDispatchSimulator {
|
|||||||
private boolean applyVehicleEvents(long tick, long now, DeterministicFaultPlan faults,
|
private boolean applyVehicleEvents(long tick, long now, DeterministicFaultPlan faults,
|
||||||
Map<String, SimulatedVehicleRuntime> vehicles,
|
Map<String, SimulatedVehicleRuntime> vehicles,
|
||||||
Map<String, ActiveTask> active,
|
Map<String, ActiveTask> active,
|
||||||
|
Map<String, ClearanceMove> clearanceMoves,
|
||||||
SimulationTaskQueue queue,
|
SimulationTaskQueue queue,
|
||||||
Map<String, Execution> executions,
|
Map<String, Execution> executions,
|
||||||
CorridorReservationManager reservations,
|
CorridorReservationManager reservations,
|
||||||
@@ -161,6 +168,12 @@ public final class HistoricalDispatchSimulator {
|
|||||||
event.getType().name(), vehicle.getCurrentSite(), vehicle.getCurrentSite()));
|
event.getType().name(), vehicle.getCurrentSite(), vehicle.getCurrentSite()));
|
||||||
changed = true;
|
changed = true;
|
||||||
if (!vehicle.isAssignmentEligible()) {
|
if (!vehicle.isAssignmentEligible()) {
|
||||||
|
ClearanceMove clearance = clearanceMoves.remove(vehicle.getVehicleId());
|
||||||
|
if (clearance != null) {
|
||||||
|
events.add(event(tick, now, "CLEARANCE_HOLD", clearance.getOwnerTaskId(),
|
||||||
|
vehicle.getVehicleId(), event.getType().name(),
|
||||||
|
vehicle.getCurrentSite(), null));
|
||||||
|
}
|
||||||
ActiveTask stopped = active.remove(vehicle.getVehicleId());
|
ActiveTask stopped = active.remove(vehicle.getVehicleId());
|
||||||
if (stopped != null) {
|
if (stopped != null) {
|
||||||
reservations.releaseTask(stopped.execution.task.getSimulationTaskId());
|
reservations.releaseTask(stopped.execution.task.getSimulationTaskId());
|
||||||
@@ -179,6 +192,7 @@ public final class HistoricalDispatchSimulator {
|
|||||||
SimulationTaskQueue queue,
|
SimulationTaskQueue queue,
|
||||||
Map<String, SimulatedVehicleRuntime> vehicles,
|
Map<String, SimulatedVehicleRuntime> vehicles,
|
||||||
Map<String, ActiveTask> active,
|
Map<String, ActiveTask> active,
|
||||||
|
Map<String, ClearanceMove> clearanceMoves,
|
||||||
Map<String, Execution> executions,
|
Map<String, Execution> executions,
|
||||||
Map<String, SimulationTaskResult> terminal,
|
Map<String, SimulationTaskResult> terminal,
|
||||||
GridPathPlanner planner,
|
GridPathPlanner planner,
|
||||||
@@ -205,7 +219,8 @@ 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, List<String>> approaches = new LinkedHashMap<String, List<String>>();
|
||||||
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())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
List<String> approach = planner.shortestPath(input.getGridMap(),
|
List<String> approach = planner.shortestPath(input.getGridMap(),
|
||||||
@@ -286,6 +301,8 @@ public final class HistoricalDispatchSimulator {
|
|||||||
IdempotentWmsCompletionReceiver wms,
|
IdempotentWmsCompletionReceiver wms,
|
||||||
SimulationInvariantSummary invariants,
|
SimulationInvariantSummary invariants,
|
||||||
CorridorReservationManager reservations,
|
CorridorReservationManager reservations,
|
||||||
|
Map<String, ClearanceMove> clearanceMoves,
|
||||||
|
ClearanceMovePlanner clearancePlanner,
|
||||||
List<SimulationTickEvent> events,
|
List<SimulationTickEvent> events,
|
||||||
Set<String> traversedEdges) {
|
Set<String> traversedEdges) {
|
||||||
boolean progress = false;
|
boolean progress = false;
|
||||||
@@ -314,6 +331,18 @@ public final class HistoricalDispatchSimulator {
|
|||||||
}
|
}
|
||||||
List<ResourceKey> resources = Arrays.asList(
|
List<ResourceKey> resources = Arrays.asList(
|
||||||
ResourceKey.node(from), edge, ResourceKey.node(to));
|
ResourceKey.node(from), edge, ResourceKey.node(to));
|
||||||
|
Set<String> idleBlockers = idleVehiclesAt(to, vehicleId, vehicles,
|
||||||
|
active, clearanceMoves);
|
||||||
|
if (!idleBlockers.isEmpty()) {
|
||||||
|
boolean clearancePlanned = planClearanceForIdleBlockers(
|
||||||
|
tick, now, current, vehicles, active, clearanceMoves,
|
||||||
|
clearancePlanner, ledger, input, idleBlockers, events);
|
||||||
|
recordHold(tick, now, current, vehicle, active, queue,
|
||||||
|
clearancePlanned ? "IDLE_VEHICLE_CLEARANCE"
|
||||||
|
: "NO_CLEARANCE_SITE",
|
||||||
|
reservations, events);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Instant decisionTime = Instant.EPOCH.plusMillis(now + tick);
|
Instant decisionTime = Instant.EPOCH.plusMillis(now + tick);
|
||||||
TrafficVehicleSnapshot frozen = new TrafficVehicleSnapshot(vehicleId,
|
TrafficVehicleSnapshot frozen = new TrafficVehicleSnapshot(vehicleId,
|
||||||
Long.toString(vehicle.getBootId()), tick + 1, tick + 1,
|
Long.toString(vehicle.getBootId()), tick + 1, tick + 1,
|
||||||
@@ -340,9 +369,14 @@ public final class HistoricalDispatchSimulator {
|
|||||||
}
|
}
|
||||||
ResourceLedger.ProtectionResult protection = ledger.evaluate(vehicleId, resources);
|
ResourceLedger.ProtectionResult protection = ledger.evaluate(vehicleId, resources);
|
||||||
if (!protection.isGranted()) {
|
if (!protection.isGranted()) {
|
||||||
invariants.capacityViolation();
|
boolean clearancePlanned = planClearanceForIdleBlockers(
|
||||||
|
tick, now, current, vehicles, active, clearanceMoves,
|
||||||
|
clearancePlanner, ledger, input,
|
||||||
|
protection.getBlockingVehicles(), events);
|
||||||
recordHold(tick, now, current, vehicle, active, queue,
|
recordHold(tick, now, current, vehicle, active, queue,
|
||||||
"AUTHORITATIVE_RESOURCE_REJECTED", reservations, events);
|
clearancePlanned ? "IDLE_VEHICLE_CLEARANCE"
|
||||||
|
: "AUTHORITATIVE_RESOURCE_REJECTED",
|
||||||
|
reservations, events);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
SimulatedPlcGateway.PlcResponse sent = plc.send(
|
SimulatedPlcGateway.PlcResponse sent = plc.send(
|
||||||
@@ -387,6 +421,152 @@ public final class HistoricalDispatchSimulator {
|
|||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void advanceClearanceMoves(long tick, long now,
|
||||||
|
HistoricalSimulationInput input,
|
||||||
|
SimulationConfig config,
|
||||||
|
Map<String, SimulatedVehicleRuntime> vehicles,
|
||||||
|
Map<String, ClearanceMove> clearanceMoves,
|
||||||
|
ResourceLedger ledger,
|
||||||
|
SimulatedPlcGateway plc,
|
||||||
|
List<SimulationTickEvent> events,
|
||||||
|
Set<String> traversedEdges) {
|
||||||
|
List<String> vehicleIds = new ArrayList<String>(clearanceMoves.keySet());
|
||||||
|
Collections.sort(vehicleIds);
|
||||||
|
for (String vehicleId : vehicleIds) {
|
||||||
|
ClearanceMove clearance = clearanceMoves.get(vehicleId);
|
||||||
|
SimulatedVehicleRuntime vehicle = vehicles.get(vehicleId);
|
||||||
|
if (clearance == null || vehicle == null || !vehicle.isAssignmentEligible()) continue;
|
||||||
|
if (clearance.isComplete()) {
|
||||||
|
clearanceMoves.remove(vehicleId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String from = clearance.currentSite();
|
||||||
|
String to = clearance.nextSite();
|
||||||
|
String direction = from + "->" + to;
|
||||||
|
if (traversedEdges.contains(to + "->" + from)
|
||||||
|
|| traversedEdges.contains(direction)) {
|
||||||
|
events.add(event(tick, now, "CLEARANCE_HOLD", clearance.getOwnerTaskId(),
|
||||||
|
vehicleId, "OPPOSITE_EDGE_WINDOW", from, to));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ResourceKey edge = ResourceKey.edge(from, to);
|
||||||
|
List<ResourceKey> resources = Arrays.asList(
|
||||||
|
ResourceKey.node(from), edge, ResourceKey.node(to));
|
||||||
|
Instant decisionTime = Instant.EPOCH.plusMillis(now + tick);
|
||||||
|
TrafficVehicleSnapshot frozen = new TrafficVehicleSnapshot(vehicleId,
|
||||||
|
Long.toString(vehicle.getBootId()), tick + 1, tick + 1,
|
||||||
|
decisionTime, from, to,
|
||||||
|
clearance.getRoute().subList(clearance.getIndex(), clearance.getRoute().size()),
|
||||||
|
true, false, false);
|
||||||
|
TrafficSnapshot snapshot = new TrafficSnapshot(tick + 1, decisionTime,
|
||||||
|
input.getTrafficMap().getMapCode(), input.getTrafficMap().getVersionId(),
|
||||||
|
"history-clearance-v1", Collections.singletonList(frozen));
|
||||||
|
ShadowCandidate candidate = ShadowCandidate.waiting(clearance.clearanceTaskId(),
|
||||||
|
vehicleId, false, Integer.MAX_VALUE, now,
|
||||||
|
clearance.getRoute().size() - clearance.getIndex(), resources, null);
|
||||||
|
ShadowTrafficCoordinator coordinator = new ShadowTrafficCoordinator(
|
||||||
|
input.getTrafficMap(), ledger, new RollingDispatchPolicy());
|
||||||
|
ShadowDecision decision = coordinator.run(snapshot,
|
||||||
|
Collections.singletonList(candidate)).getDecisions().get(0);
|
||||||
|
if (decision.getType() != DecisionType.GRANT) {
|
||||||
|
events.add(event(tick, now, "CLEARANCE_HOLD", clearance.getOwnerTaskId(),
|
||||||
|
vehicleId, decision.getReasonCode(), from, to));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ResourceLedger.ProtectionResult protection = ledger.evaluate(vehicleId, resources);
|
||||||
|
if (!protection.isGranted()) {
|
||||||
|
events.add(event(tick, now, "CLEARANCE_HOLD", clearance.getOwnerTaskId(),
|
||||||
|
vehicleId, "AUTHORITATIVE_RESOURCE_REJECTED", from, to));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
clearance.attemptedProtocol();
|
||||||
|
SimulatedPlcGateway.PlcResponse sent = plc.send(
|
||||||
|
clearance.clearanceTaskId(), vehicle, from, to);
|
||||||
|
SimulatedPlcGateway.PlcResponse accepted = plc.accept(sent, vehicle);
|
||||||
|
if (accepted.getReason() != SimulatedPlcGateway.PlcReason.ACK) {
|
||||||
|
ledger.release(vehicleId, Arrays.asList(edge, ResourceKey.node(to)));
|
||||||
|
events.add(event(tick, now, "CLEARANCE_PLC_RETRY",
|
||||||
|
clearance.getOwnerTaskId(), vehicleId,
|
||||||
|
accepted.getReason().name(), from, to));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Site nextSite = input.getGridMap().getSite(to);
|
||||||
|
vehicle.moveTo(to, nextSite.getFloor());
|
||||||
|
ledger.release(vehicleId, Arrays.asList(ResourceKey.node(from), edge));
|
||||||
|
traversedEdges.add(direction);
|
||||||
|
clearance.moved();
|
||||||
|
events.add(event(tick, now, "CLEARANCE_MOVE", clearance.getOwnerTaskId(),
|
||||||
|
vehicleId, "ACK", from, to));
|
||||||
|
if (clearance.isComplete()) {
|
||||||
|
clearanceMoves.remove(vehicleId);
|
||||||
|
events.add(event(tick, now, "CLEARANCE_COMPLETE",
|
||||||
|
clearance.getOwnerTaskId(), vehicleId, "ROUTE_CLEARED",
|
||||||
|
to, to));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean planClearanceForIdleBlockers(
|
||||||
|
long tick, long now, ActiveTask owner,
|
||||||
|
Map<String, SimulatedVehicleRuntime> vehicles,
|
||||||
|
Map<String, ActiveTask> active,
|
||||||
|
Map<String, ClearanceMove> clearanceMoves,
|
||||||
|
ClearanceMovePlanner clearancePlanner,
|
||||||
|
ResourceLedger ledger,
|
||||||
|
HistoricalSimulationInput input,
|
||||||
|
Set<String> blockingVehicleIds,
|
||||||
|
List<SimulationTickEvent> events) {
|
||||||
|
List<String> blockers = new ArrayList<String>(blockingVehicleIds);
|
||||||
|
Collections.sort(blockers);
|
||||||
|
Set<String> occupiedSites = new LinkedHashSet<String>();
|
||||||
|
for (SimulatedVehicleRuntime vehicle : vehicles.values()) {
|
||||||
|
occupiedSites.add(vehicle.getCurrentSite());
|
||||||
|
}
|
||||||
|
Set<ResourceKey> reservedResources = new LinkedHashSet<ResourceKey>();
|
||||||
|
for (Map.Entry<ResourceKey, Set<String>> entry : ledger.snapshotHolders().entrySet()) {
|
||||||
|
if (!entry.getValue().isEmpty()) reservedResources.add(entry.getKey());
|
||||||
|
}
|
||||||
|
for (String blockerId : blockers) {
|
||||||
|
SimulatedVehicleRuntime blocker = vehicles.get(blockerId);
|
||||||
|
if (blocker == null || !blocker.isAssignmentEligible()
|
||||||
|
|| active.containsKey(blockerId) || clearanceMoves.containsKey(blockerId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
java.util.Optional<ClearanceMove> planned = clearancePlanner.plan(
|
||||||
|
blockerId, blocker.getCurrentSite(),
|
||||||
|
owner.execution.task.getSimulationTaskId(),
|
||||||
|
owner.route.subList(owner.index, owner.route.size()),
|
||||||
|
occupiedSites, reservedResources, input.getGridMap());
|
||||||
|
if (planned.isPresent()) {
|
||||||
|
ClearanceMove clearance = planned.get();
|
||||||
|
clearanceMoves.put(blockerId, clearance);
|
||||||
|
events.add(event(tick, now, "CLEARANCE_PLANNED",
|
||||||
|
owner.execution.task.getSimulationTaskId(), blockerId,
|
||||||
|
"IDLE_BLOCKER", clearance.getSourceSite(), clearance.getTargetSite()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> idleVehiclesAt(String siteId, String movingVehicleId,
|
||||||
|
Map<String, SimulatedVehicleRuntime> vehicles,
|
||||||
|
Map<String, ActiveTask> active,
|
||||||
|
Map<String, ClearanceMove> clearanceMoves) {
|
||||||
|
Set<String> blockers = new LinkedHashSet<String>();
|
||||||
|
for (SimulatedVehicleRuntime candidate : vehicles.values()) {
|
||||||
|
String candidateId = candidate.getVehicleId();
|
||||||
|
if (!candidateId.equals(movingVehicleId)
|
||||||
|
&& siteId.equals(candidate.getCurrentSite())
|
||||||
|
&& candidate.isAssignmentEligible()
|
||||||
|
&& !active.containsKey(candidateId)
|
||||||
|
&& !clearanceMoves.containsKey(candidateId)) {
|
||||||
|
blockers.add(candidateId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blockers;
|
||||||
|
}
|
||||||
|
|
||||||
private int queuePriority(Execution execution, long now, SimulationConfig config) {
|
private int queuePriority(Execution execution, long now, SimulationConfig config) {
|
||||||
long waited = Math.max(0L, now - execution.arrivalMillis);
|
long waited = Math.max(0L, now - execution.arrivalMillis);
|
||||||
return (int) Math.max(0L, (long) execution.task.getPriority()
|
return (int) Math.max(0L, (long) execution.task.getPriority()
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.ft546.wcs.shadow.history.simulation;
|
||||||
|
|
||||||
|
import com.ft546.wcs.demo.model.Site;
|
||||||
|
import com.ft546.wcs.demo.traffic.GridMap;
|
||||||
|
import com.ft546.wcs.shadow.resource.ResourceKey;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class ClearanceMovePlannerTest {
|
||||||
|
@Test
|
||||||
|
void selectsAReachableOffRouteUnoccupiedSite() {
|
||||||
|
GridMap map = new GridMap();
|
||||||
|
for (String site : Arrays.asList("A", "B", "C", "X")) {
|
||||||
|
map.addSite(new Site(site, 1, site.charAt(0), 1, "TEST"));
|
||||||
|
}
|
||||||
|
map.connect("A", "B");
|
||||||
|
map.connect("B", "C");
|
||||||
|
map.connect("B", "X");
|
||||||
|
|
||||||
|
Optional<ClearanceMove> planned = new ClearanceMovePlanner().plan(
|
||||||
|
"3", "B", "OWNER", Arrays.asList("A", "B", "C"),
|
||||||
|
new LinkedHashSet<String>(Arrays.asList("A", "B")),
|
||||||
|
Collections.singleton(ResourceKey.node("C")), map);
|
||||||
|
|
||||||
|
assertTrue(planned.isPresent());
|
||||||
|
assertEquals("X", planned.get().getTargetSite());
|
||||||
|
assertEquals(Arrays.asList("B", "X"), planned.get().getRoute());
|
||||||
|
assertEquals("OWNER", planned.get().getOwnerTaskId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,6 +68,35 @@ class HistoricalDispatchSimulatorTest {
|
|||||||
assertEquals(1, result.getReplannedCount());
|
assertEquals(1, result.getReplannedCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void movesIdleBlockersNodeByNodeAndCompletesBothBusinessDirections() {
|
||||||
|
HistoricalSimulationInput input = blockedThreeVehicleCorridorInput();
|
||||||
|
|
||||||
|
HistoricalDispatchSimulationResult result = new HistoricalDispatchSimulator().simulate(
|
||||||
|
input, new SimulationConfig(100, 30_000L, 3, 200, 500),
|
||||||
|
DeterministicFaultPlan.none());
|
||||||
|
|
||||||
|
assertEquals(2, result.getCompletedCount(), () -> result.canonicalView() + "\n"
|
||||||
|
+ result.getEvents().stream()
|
||||||
|
.map(event -> event.getTick() + ":" + event.getType() + ":"
|
||||||
|
+ event.getTaskId() + ":" + event.getVehicleId() + ":"
|
||||||
|
+ event.getReason() + ":" + event.getFromSite() + "->"
|
||||||
|
+ event.getToSite())
|
||||||
|
.collect(java.util.stream.Collectors.joining("\n")));
|
||||||
|
assertEquals(0, result.getWaitingCount());
|
||||||
|
assertTrue(result.getEvents().stream()
|
||||||
|
.anyMatch(event -> "CLEARANCE_PLANNED".equals(event.getType())));
|
||||||
|
assertTrue(result.getEvents().stream()
|
||||||
|
.anyMatch(event -> "CLEARANCE_MOVE".equals(event.getType())));
|
||||||
|
result.getEvents().stream()
|
||||||
|
.filter(event -> "CLEARANCE_MOVE".equals(event.getType()))
|
||||||
|
.forEach(event -> assertTrue(input.getGridMap().getNeighbors(event.getFromSite())
|
||||||
|
.contains(event.getToSite())));
|
||||||
|
assertEquals(0, result.getInvariants().getOppositeEdgeViolations());
|
||||||
|
assertEquals(0, result.getInvariants().getNodeDoubleOccupancyViolations());
|
||||||
|
assertEquals(0, result.getInvariants().getCapacityViolations());
|
||||||
|
}
|
||||||
|
|
||||||
static HistoricalSimulationInput controlledInput() {
|
static HistoricalSimulationInput controlledInput() {
|
||||||
GridMap grid = new GridMap();
|
GridMap grid = new GridMap();
|
||||||
for (String node : Arrays.asList("A", "B", "C", "D")) {
|
for (String node : Arrays.asList("A", "B", "C", "D")) {
|
||||||
@@ -151,6 +180,39 @@ class HistoricalDispatchSimulatorTest {
|
|||||||
"1", 1, "A", 5, 1, 100, true, true, false)));
|
"1", 1, "A", 5, 1, 100, true, true, false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HistoricalSimulationInput blockedThreeVehicleCorridorInput() {
|
||||||
|
GridMap grid = new GridMap();
|
||||||
|
for (String node : Arrays.asList("A", "B", "C", "D", "X", "Z")) {
|
||||||
|
grid.addSite(new Site(node, 1, node.charAt(0), 1, "TEST"));
|
||||||
|
}
|
||||||
|
grid.connect("A", "B");
|
||||||
|
grid.connect("B", "C");
|
||||||
|
grid.connect("C", "D");
|
||||||
|
grid.connect("B", "X");
|
||||||
|
grid.connect("D", "Z");
|
||||||
|
LinkedHashSet<String> nodes = new LinkedHashSet<String>(
|
||||||
|
Arrays.asList("A", "B", "C", "D", "X", "Z"));
|
||||||
|
LinkedHashSet<TrafficMapCompiler.EdgeDefinition> edges =
|
||||||
|
new LinkedHashSet<TrafficMapCompiler.EdgeDefinition>();
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("A", "B"));
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("B", "C"));
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("C", "D"));
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("B", "X"));
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("D", "Z"));
|
||||||
|
EffectiveTrafficMap traffic = new TrafficMapCompiler().compile(
|
||||||
|
new TrafficMapCompiler.MapDefinition("BLOCKED", 1L, "TEST", true,
|
||||||
|
"test", "test", nodes, edges, Collections.emptyMap(),
|
||||||
|
Collections.emptySet(), Collections.emptySet()));
|
||||||
|
List<HistoricalSimulationTask> tasks = Arrays.asList(
|
||||||
|
customTask("OWNER", "A", "D", 1, 0, Arrays.asList("A", "B", "C", "D")),
|
||||||
|
customTask("RETURN", "D", "A", 2, 0, Arrays.asList("D", "C", "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),
|
||||||
|
new VehicleSnapshot("3", 1, "B", 5, 1, 100, true, true, false));
|
||||||
|
return new HistoricalSimulationInput(tasks, Collections.emptyList(), grid, traffic, vehicles);
|
||||||
|
}
|
||||||
|
|
||||||
private static HistoricalSimulationTask task(String id, String source, String target,
|
private static HistoricalSimulationTask task(String id, String source, String target,
|
||||||
int priority, int sequence) {
|
int priority, int sequence) {
|
||||||
List<String> path = "T2".equals(id)
|
List<String> path = "T2".equals(id)
|
||||||
|
|||||||
Reference in New Issue
Block a user