Refactor ElkSharp hybrid routing and document speed path

This commit is contained in:
master
2026-03-29 19:33:46 +03:00
parent 7d6bc2b0ab
commit e8f7ad7652
89 changed files with 13280 additions and 10732 deletions

View File

@@ -0,0 +1,207 @@
namespace StellaOps.ElkSharp;
internal static partial class ElkEdgeRouterIterative
{
private static ElkRoutedEdge[] ApplyFinalDetourPolish(
ElkRoutedEdge[] edges,
ElkPositionedNode[] nodes,
double minLineClearance,
IReadOnlyCollection<string>? 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<string, int>(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 focused = (IReadOnlyCollection<string>)[edgeId];
var candidateEdges = ComposeTransactionalFinalDetourCandidate(
result,
nodes,
minLineClearance,
focused);
candidateEdges = ChoosePreferredHardRuleLayout(result, candidateEdges, nodes);
if (ReferenceEquals(candidateEdges, result))
{
continue;
}
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
var candidateRetryState = BuildRetryState(
candidateScore,
HighwayProcessingEnabled
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
: 0);
var improvedDetours = candidateRetryState.ExcessiveDetourViolations < currentRetryState.ExcessiveDetourViolations;
if (HasHardRuleRegression(candidateRetryState, currentRetryState)
|| (!improvedDetours
&& !IsBetterBoundarySlotRepairCandidate(
candidateScore,
candidateRetryState,
currentScore,
currentRetryState)))
{
continue;
}
result = candidateEdges;
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<string> 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 candidate;
}
internal static ElkRoutedEdge[] ApplyPostSlotDetourClosure(
ElkRoutedEdge[] edges,
ElkPositionedNode[] nodes,
double minLineClearance,
IReadOnlyCollection<string>? 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);
}
}