Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.StrategyRepair.RepairPlan.cs

374 lines
16 KiB
C#

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());
}
}