namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { private static ElkRoutedEdge[] ApplyFinalDetourPolish( ElkRoutedEdge[] edges, ElkPositionedNode[] nodes, ElkLayoutDirection direction, double minLineClearance, IReadOnlyCollection? restrictedEdgeIds) { var restrictedSet = restrictedEdgeIds is null ? null : restrictedEdgeIds.ToHashSet(StringComparer.Ordinal); var result = edges; for (var round = 0; round < 3; round++) { var detourSeverity = new Dictionary(StringComparer.Ordinal); ElkEdgeRoutingScoring.CountExcessiveDetourViolations(result, nodes, detourSeverity, 10); if (detourSeverity.Count == 0) { break; } var currentScore = ElkEdgeRoutingScoring.ComputeScore(result, nodes); var currentRetryState = BuildRetryState( currentScore, HighwayProcessingEnabled ? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(result, nodes).Count : 0); var improved = false; foreach (var edgeId in detourSeverity .OrderByDescending(pair => pair.Value) .ThenBy(pair => pair.Key, StringComparer.Ordinal) .Select(pair => pair.Key)) { if (restrictedSet is not null && !restrictedSet.Contains(edgeId)) { continue; } var directFocus = (IReadOnlyCollection)[edgeId]; var expandedFocus = ExpandWinningSolutionFocus(result, [edgeId]) .Where(id => restrictedSet is null || restrictedSet.Contains(id)) .OrderBy(id => id, StringComparer.Ordinal) .ToArray(); if (expandedFocus.Length == 0) { expandedFocus = [edgeId]; } var bestCandidateEdges = result; var bestCandidateScore = currentScore; var bestCandidateRetryState = currentRetryState; void ConsiderDetourCandidate(ElkRoutedEdge[] candidate) { candidate = ChoosePreferredHardRuleLayout(result, candidate, nodes); if (ReferenceEquals(candidate, result)) { return; } var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidate, nodes); var candidateRetryState = BuildRetryState( candidateScore, HighwayProcessingEnabled ? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidate, nodes).Count : 0); var improvedDetours = candidateRetryState.ExcessiveDetourViolations < currentRetryState.ExcessiveDetourViolations; if (HasHardRuleRegression(candidateRetryState, currentRetryState) || (!improvedDetours && !IsBetterBoundarySlotRepairCandidate( candidateScore, candidateRetryState, currentScore, currentRetryState))) { return; } if (ReferenceEquals(bestCandidateEdges, result) || IsBetterCandidate(candidateScore, candidateRetryState, bestCandidateScore, bestCandidateRetryState)) { bestCandidateEdges = candidate; bestCandidateScore = candidateScore; bestCandidateRetryState = candidateRetryState; } } ConsiderDetourCandidate( ComposeDirectionalTransactionalFinalDetourCandidate( result, nodes, direction, minLineClearance, directFocus)); if (expandedFocus.Length != 1 || !string.Equals(expandedFocus[0], edgeId, StringComparison.Ordinal)) { ConsiderDetourCandidate( ComposeDirectionalTransactionalFinalDetourCandidate( result, nodes, direction, minLineClearance, expandedFocus)); } var focusedSeed = ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(result, nodes, directFocus); if (!ReferenceEquals(focusedSeed, result)) { ConsiderDetourCandidate( BuildFinalRestabilizedCandidate( focusedSeed, nodes, direction, minLineClearance, expandedFocus)); } if (ReferenceEquals(bestCandidateEdges, result)) { continue; } result = bestCandidateEdges; improved = true; break; } if (!improved) { break; } } return result; } private static bool TryPromoteFinalDetourCandidate( ElkRoutedEdge[] baselineEdges, ElkRoutedEdge[] candidateEdges, ElkPositionedNode[] nodes, EdgeRoutingScore baselineScore, RoutingRetryState baselineRetryState, out ElkRoutedEdge[] promotedEdges) { promotedEdges = baselineEdges; if (ReferenceEquals(candidateEdges, baselineEdges)) { return false; } var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes); var candidateRetryState = BuildRetryState( candidateScore, HighwayProcessingEnabled ? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count : 0); var improvedDetours = candidateRetryState.ExcessiveDetourViolations < baselineRetryState.ExcessiveDetourViolations; var improvedGatewaySource = candidateRetryState.GatewaySourceExitViolations < baselineRetryState.GatewaySourceExitViolations; if (HasHardRuleRegression(candidateRetryState, baselineRetryState) || (!(improvedDetours || improvedGatewaySource) && !IsBetterBoundarySlotRepairCandidate( candidateScore, candidateRetryState, baselineScore, baselineRetryState))) { return false; } promotedEdges = candidateEdges; return true; } private static ElkRoutedEdge[] ComposeTransactionalFinalDetourCandidate( ElkRoutedEdge[] baseline, ElkPositionedNode[] nodes, double minLineClearance, IReadOnlyCollection focusedEdgeIds) { return ComposeDirectionalTransactionalFinalDetourCandidate( baseline, nodes, ElkLayoutDirection.LeftToRight, minLineClearance, focusedEdgeIds); } private static ElkRoutedEdge[] ComposeDirectionalTransactionalFinalDetourCandidate( ElkRoutedEdge[] baseline, ElkPositionedNode[] nodes, ElkLayoutDirection direction, double minLineClearance, IReadOnlyCollection focusedEdgeIds) { var candidate = ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(baseline, nodes, focusedEdgeIds); if (ReferenceEquals(candidate, baseline)) { return baseline; } candidate = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ElkEdgePostProcessor.SpreadTargetApproachJoins(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ElkEdgePostProcessor.ElevateUnderNodeViolations(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ClampBelowGraphEdges(candidate, nodes, focusedEdgeIds); candidate = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ElkEdgePostProcessor.SpreadTargetApproachJoins(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ElkEdgePostProcessor.FinalizeDecisionTargetEntries(candidate, nodes, focusedEdgeIds); candidate = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(candidate, nodes, focusedEdgeIds); candidate = ElkEdgePostProcessor.PolishTargetPeerConflicts(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, focusedEdgeIds, enforceAllNodeEndpoints: true); candidate = ApplyPostSlotDetourClosure(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ApplyLateBoundarySlotRestabilization(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts(candidate, nodes, minLineClearance, focusedEdgeIds); candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, focusedEdgeIds, enforceAllNodeEndpoints: true); candidate = ApplyPostSlotDetourClosure(candidate, nodes, minLineClearance, focusedEdgeIds); return BuildFinalRestabilizedCandidate( candidate, nodes, direction, minLineClearance, focusedEdgeIds); } internal static ElkRoutedEdge[] ApplyPostSlotDetourClosure( ElkRoutedEdge[] edges, ElkPositionedNode[] nodes, double minLineClearance, IReadOnlyCollection? restrictedEdgeIds = null) { var candidate = ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(edges, nodes, restrictedEdgeIds); if (ReferenceEquals(candidate, edges)) { return edges; } candidate = ElkEdgePostProcessor.NormalizeBoundaryAngles(candidate, nodes); candidate = ElkEdgePostProcessor.NormalizeSourceExitAngles(candidate, nodes); candidate = ElkEdgePostProcessor.SpreadSourceDepartureJoins(candidate, nodes, minLineClearance, restrictedEdgeIds); candidate = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(candidate, nodes, minLineClearance, restrictedEdgeIds); candidate = ElkEdgePostProcessor.SpreadTargetApproachJoins(candidate, nodes, minLineClearance, restrictedEdgeIds); candidate = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(candidate, nodes, minLineClearance, restrictedEdgeIds); candidate = ElkEdgePostProcessor.FinalizeDecisionTargetEntries(candidate, nodes, restrictedEdgeIds); candidate = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(candidate, nodes, restrictedEdgeIds); candidate = ElkEdgePostProcessor.PolishTargetPeerConflicts(candidate, nodes, minLineClearance, restrictedEdgeIds); candidate = ElkEdgePostProcessor.ElevateUnderNodeViolations(candidate, nodes, minLineClearance, restrictedEdgeIds); candidate = ClampBelowGraphEdges(candidate, nodes, restrictedEdgeIds); candidate = ElkEdgePostProcessor.NormalizeBoundaryAngles(candidate, nodes); candidate = ElkEdgePostProcessor.NormalizeSourceExitAngles(candidate, nodes); candidate = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(candidate, nodes, minLineClearance, restrictedEdgeIds); candidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts(candidate, nodes, minLineClearance, restrictedEdgeIds); candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true); return ElkEdgeRoutingScoring.CountBoundarySlotViolations(edges, nodes) > 0 ? ChoosePreferredBoundarySlotRepairLayout(edges, candidate, nodes) : ChoosePreferredHardRuleLayout(edges, candidate, nodes); } }