Refactor ElkSharp hybrid routing and document speed path
This commit is contained in:
@@ -0,0 +1,373 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace StellaOps.ElkSharp;
|
||||
|
||||
internal static partial class ElkEdgeRouterIterative
|
||||
{
|
||||
private static RepairPlan? BuildRepairPlan(
|
||||
ElkRoutedEdge[] edges,
|
||||
ElkPositionedNode[] nodes,
|
||||
EdgeRoutingScore score,
|
||||
RoutingRetryState retryState,
|
||||
RoutingStrategy strategy,
|
||||
int attempt)
|
||||
{
|
||||
if (edges.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var severityByEdgeId = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
var preferredShortestEdgeIds = new HashSet<string>(StringComparer.Ordinal);
|
||||
var routeRepairEdgeIds = new HashSet<string>(StringComparer.Ordinal);
|
||||
var mandatoryEdgeIds = new HashSet<string>(StringComparer.Ordinal);
|
||||
var severityByReason = new Dictionary<string, Dictionary<string, int>>(StringComparer.Ordinal);
|
||||
var reasons = new List<string>();
|
||||
var prioritizeBlockingAndLengthOnly = retryState.RequiresBlockingRetry || retryState.RequiresLengthRetry;
|
||||
|
||||
void AddReason(string reason)
|
||||
{
|
||||
if (!reasons.Contains(reason, StringComparer.Ordinal))
|
||||
{
|
||||
reasons.Add(reason);
|
||||
}
|
||||
}
|
||||
|
||||
void AddEdgeIds(
|
||||
IEnumerable<string> edgeIds,
|
||||
int severity,
|
||||
string reason,
|
||||
bool requiresRouteRepair = false,
|
||||
bool mandatoryRepair = false)
|
||||
{
|
||||
AddReason(reason);
|
||||
if (!severityByReason.TryGetValue(reason, out var reasonSeverity))
|
||||
{
|
||||
reasonSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
severityByReason[reason] = reasonSeverity;
|
||||
}
|
||||
|
||||
foreach (var edgeId in edgeIds)
|
||||
{
|
||||
severityByEdgeId[edgeId] = severityByEdgeId.GetValueOrDefault(edgeId) + severity;
|
||||
reasonSeverity[edgeId] = reasonSeverity.GetValueOrDefault(edgeId) + severity;
|
||||
if (requiresRouteRepair)
|
||||
{
|
||||
routeRepairEdgeIds.Add(edgeId);
|
||||
}
|
||||
|
||||
if (mandatoryRepair)
|
||||
{
|
||||
mandatoryEdgeIds.Add(edgeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MergeSeverity(
|
||||
Dictionary<string, int> metricSeverity,
|
||||
string reason,
|
||||
bool preferShortestRepair = false,
|
||||
bool requiresRouteRepair = false,
|
||||
bool mandatoryRepair = false)
|
||||
{
|
||||
if (metricSeverity.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddReason(reason);
|
||||
if (!severityByReason.TryGetValue(reason, out var reasonSeverity))
|
||||
{
|
||||
reasonSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
severityByReason[reason] = reasonSeverity;
|
||||
}
|
||||
|
||||
foreach (var (edgeId, severity) in metricSeverity)
|
||||
{
|
||||
severityByEdgeId[edgeId] = severityByEdgeId.GetValueOrDefault(edgeId) + severity;
|
||||
reasonSeverity[edgeId] = reasonSeverity.GetValueOrDefault(edgeId) + severity;
|
||||
if (preferShortestRepair || requiresRouteRepair)
|
||||
{
|
||||
routeRepairEdgeIds.Add(edgeId);
|
||||
}
|
||||
|
||||
if (mandatoryRepair)
|
||||
{
|
||||
mandatoryEdgeIds.Add(edgeId);
|
||||
}
|
||||
|
||||
if (preferShortestRepair)
|
||||
{
|
||||
preferredShortestEdgeIds.Add(edgeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attempt == 1 && retryState.RequiresLengthRetry && !retryState.RequiresBlockingRetry)
|
||||
{
|
||||
if (retryState.TargetApproachBacktrackingViolations > 0)
|
||||
{
|
||||
var backtrackingSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountTargetApproachBacktrackingViolations(edges, nodes, backtrackingSeverity, 2_250);
|
||||
MergeSeverity(backtrackingSeverity, "approach-backtracking", preferShortestRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.ExcessiveDetourViolations > 0)
|
||||
{
|
||||
var detourSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountExcessiveDetourViolations(edges, nodes, detourSeverity, 2_000);
|
||||
MergeSeverity(detourSeverity, "detour-priority", preferShortestRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (score.NodeCrossings > 0)
|
||||
{
|
||||
var nodeCrossingSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountEdgeNodeCrossings(edges, nodes, nodeCrossingSeverity, 2_500);
|
||||
MergeSeverity(nodeCrossingSeverity, "node-crossings", requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.RemainingShortHighways > 0)
|
||||
{
|
||||
var brokenHighways = ElkEdgeRouterHighway.DetectRemainingBrokenHighways(edges, nodes);
|
||||
AddEdgeIds(
|
||||
brokenHighways.SelectMany(highway => highway.EdgeIds).Distinct(StringComparer.Ordinal),
|
||||
2_000,
|
||||
"short-highways",
|
||||
requiresRouteRepair: true,
|
||||
mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.RepeatCollectorCorridorViolations > 0)
|
||||
{
|
||||
var collectorSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountRepeatCollectorCorridorViolations(edges, nodes, collectorSeverity, 2_000);
|
||||
MergeSeverity(collectorSeverity, "collector-corridors", requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.RepeatCollectorNodeClearanceViolations > 0)
|
||||
{
|
||||
var collectorClearanceSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountRepeatCollectorNodeClearanceViolations(edges, nodes, collectorClearanceSeverity, 2_000);
|
||||
MergeSeverity(collectorClearanceSeverity, "collector-clearance", requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.TargetApproachJoinViolations > 0)
|
||||
{
|
||||
var targetJoinSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(edges, nodes, targetJoinSeverity, 1_500);
|
||||
MergeSeverity(targetJoinSeverity, "target-joins", requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.TargetApproachBacktrackingViolations > 0)
|
||||
{
|
||||
var backtrackingSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountTargetApproachBacktrackingViolations(edges, nodes, backtrackingSeverity, 1_600);
|
||||
MergeSeverity(backtrackingSeverity, "approach-backtracking", preferShortestRepair: true, requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.BelowGraphViolations > 0)
|
||||
{
|
||||
var belowGraphSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountBelowGraphViolations(edges, nodes, belowGraphSeverity, 2_500);
|
||||
MergeSeverity(belowGraphSeverity, "below-graph", preferShortestRepair: true, requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.UnderNodeViolations > 0)
|
||||
{
|
||||
var underNodeSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountUnderNodeViolations(edges, nodes, underNodeSeverity, 2_500);
|
||||
MergeSeverity(underNodeSeverity, "under-node", preferShortestRepair: true, requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.LongDiagonalViolations > 0)
|
||||
{
|
||||
var longDiagonalSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountLongDiagonalViolations(edges, nodes, longDiagonalSeverity, 2_250);
|
||||
MergeSeverity(longDiagonalSeverity, "long-diagonal", preferShortestRepair: true, requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.ExcessiveDetourViolations > 0)
|
||||
{
|
||||
var detourSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountExcessiveDetourViolations(edges, nodes, detourSeverity, 1_250);
|
||||
MergeSeverity(detourSeverity, "detour", preferShortestRepair: true, requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.SharedLaneViolations > 0)
|
||||
{
|
||||
var sharedLaneSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountSharedLaneViolations(edges, nodes, sharedLaneSeverity, 2_000);
|
||||
MergeSeverity(sharedLaneSeverity, "shared-lanes", requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.BoundarySlotViolations > 0)
|
||||
{
|
||||
var boundarySlotSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountBoundarySlotViolations(edges, nodes, boundarySlotSeverity, 2_000);
|
||||
MergeSeverity(boundarySlotSeverity, "boundary-slots", requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (!prioritizeBlockingAndLengthOnly && retryState.ProximityViolations > 0)
|
||||
{
|
||||
var proximitySeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountProximityViolations(edges, nodes, proximitySeverity, 350);
|
||||
MergeSeverity(proximitySeverity, "proximity", requiresRouteRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.EntryAngleViolations > 0)
|
||||
{
|
||||
var entrySeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountBadBoundaryAngles(edges, nodes, entrySeverity, 450);
|
||||
MergeSeverity(entrySeverity, "entry", requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (retryState.GatewaySourceExitViolations > 0)
|
||||
{
|
||||
var gatewaySourceSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(edges, nodes, gatewaySourceSeverity, 2_500);
|
||||
MergeSeverity(gatewaySourceSeverity, "gateway-source-exit", preferShortestRepair: true, requiresRouteRepair: true, mandatoryRepair: true);
|
||||
}
|
||||
|
||||
if (!prioritizeBlockingAndLengthOnly && retryState.LabelProximityViolations > 0)
|
||||
{
|
||||
var labelSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountLabelProximityViolations(edges, nodes, labelSeverity, 300);
|
||||
MergeSeverity(labelSeverity, "label", requiresRouteRepair: true);
|
||||
}
|
||||
|
||||
if (!prioritizeBlockingAndLengthOnly && retryState.EdgeCrossings > 0)
|
||||
{
|
||||
var edgeCrossingSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountEdgeEdgeCrossings(edges, edgeCrossingSeverity, 200);
|
||||
MergeSeverity(edgeCrossingSeverity, "edge-crossings", requiresRouteRepair: true);
|
||||
}
|
||||
}
|
||||
|
||||
if (severityByEdgeId.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var orderRankByEdgeId = strategy.EdgeOrder
|
||||
.Select((edgeIndex, rank) => new { edgeIndex, rank })
|
||||
.Where(item => item.edgeIndex >= 0 && item.edgeIndex < edges.Length)
|
||||
.ToDictionary(item => edges[item.edgeIndex].Id, item => item.rank, StringComparer.Ordinal);
|
||||
var seedSelectedEdgeIds = new List<string>();
|
||||
foreach (var reason in reasons)
|
||||
{
|
||||
if (!severityByReason.TryGetValue(reason, out var reasonSeverity)
|
||||
|| reasonSeverity.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var picksForReason = reason is "detour" or "detour-priority" or "approach-backtracking"
|
||||
? 2
|
||||
: 1;
|
||||
var rankedReasonEdgeIds = reasonSeverity
|
||||
.OrderByDescending(pair => pair.Value)
|
||||
.ThenBy(pair => orderRankByEdgeId.GetValueOrDefault(pair.Key, int.MaxValue))
|
||||
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
||||
.Select(pair => pair.Key)
|
||||
.ToArray();
|
||||
foreach (var edgeId in RotateOrderedEdgeIds(rankedReasonEdgeIds, attempt))
|
||||
{
|
||||
if (seedSelectedEdgeIds.Contains(edgeId, StringComparer.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
seedSelectedEdgeIds.Add(edgeId);
|
||||
if (--picksForReason == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var maxEdgeRepairs = DetermineRepairEdgeBudget(
|
||||
retryState,
|
||||
attempt == 1 && retryState.RequiresLengthRetry,
|
||||
seedSelectedEdgeIds.Count,
|
||||
mandatoryEdgeIds.Count);
|
||||
var orderedEdgeIds = severityByEdgeId
|
||||
.OrderByDescending(pair => pair.Value)
|
||||
.ThenBy(pair => orderRankByEdgeId.GetValueOrDefault(pair.Key, int.MaxValue))
|
||||
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
||||
.Select(pair => pair.Key)
|
||||
.ToArray();
|
||||
var prioritizedShortestEdgeIds = orderedEdgeIds
|
||||
.Where(preferredShortestEdgeIds.Contains)
|
||||
.Take(Math.Min(Math.Max(2, seedSelectedEdgeIds.Count), maxEdgeRepairs))
|
||||
.ToArray();
|
||||
var orderedMandatoryEdgeIds = orderedEdgeIds
|
||||
.Where(mandatoryEdgeIds.Contains)
|
||||
.ToArray();
|
||||
var mandatoryFocusBudget = DetermineMandatoryFocusBudget(
|
||||
orderedMandatoryEdgeIds.Length,
|
||||
maxEdgeRepairs,
|
||||
seedSelectedEdgeIds.Count);
|
||||
var focusedMandatoryEdgeIds = RotateOrderedEdgeIds(orderedMandatoryEdgeIds, attempt)
|
||||
.Take(mandatoryFocusBudget)
|
||||
.ToArray();
|
||||
var effectiveEdgeRepairBudget = Math.Max(maxEdgeRepairs, focusedMandatoryEdgeIds.Length);
|
||||
var selectedEdgeIds = focusedMandatoryEdgeIds
|
||||
.Concat(seedSelectedEdgeIds)
|
||||
.Concat(prioritizedShortestEdgeIds)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.Take(effectiveEdgeRepairBudget)
|
||||
.ToArray();
|
||||
if (reasons.Contains("collector-corridors", StringComparer.Ordinal))
|
||||
{
|
||||
selectedEdgeIds = ExpandRepeatCollectorRepairSet(selectedEdgeIds, edges, nodes);
|
||||
}
|
||||
|
||||
if (reasons.Contains("target-joins", StringComparer.Ordinal))
|
||||
{
|
||||
selectedEdgeIds = ExpandTargetApproachJoinRepairSet(selectedEdgeIds, edges, nodes, strategy.MinLineClearance);
|
||||
}
|
||||
|
||||
if (reasons.Contains("under-node", StringComparer.Ordinal))
|
||||
{
|
||||
selectedEdgeIds = ExpandUnderNodeRepairSet(selectedEdgeIds, edges, nodes);
|
||||
}
|
||||
|
||||
if (reasons.Contains("shared-lanes", StringComparer.Ordinal))
|
||||
{
|
||||
selectedEdgeIds = ExpandSharedLaneRepairSet(selectedEdgeIds, edges, nodes);
|
||||
}
|
||||
|
||||
if (selectedEdgeIds.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var preferredSelectedEdgeIds = selectedEdgeIds
|
||||
.Where(preferredShortestEdgeIds.Contains)
|
||||
.ToArray();
|
||||
var routeRepairSelectedEdgeIds = selectedEdgeIds
|
||||
.Where(routeRepairEdgeIds.Contains)
|
||||
.ToArray();
|
||||
|
||||
var edgeIndices = selectedEdgeIds
|
||||
.Select(edgeId => Array.FindIndex(edges, edge => string.Equals(edge.Id, edgeId, StringComparison.Ordinal)))
|
||||
.Where(edgeIndex => edgeIndex >= 0)
|
||||
.ToArray();
|
||||
if (edgeIndices.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new RepairPlan(
|
||||
edgeIndices,
|
||||
selectedEdgeIds,
|
||||
preferredSelectedEdgeIds,
|
||||
routeRepairSelectedEdgeIds,
|
||||
reasons.ToArray());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user