Refactor ElkSharp hybrid routing and document speed path
This commit is contained in:
@@ -9,8 +9,14 @@ internal static partial class ElkEdgeRouterIterative
|
||||
private static ElkRoutedEdge[] ApplyPostProcessing(
|
||||
ElkRoutedEdge[] edges,
|
||||
ElkPositionedNode[] nodes,
|
||||
ElkLayoutOptions layoutOptions)
|
||||
ElkLayoutOptions layoutOptions,
|
||||
bool preferHybridTerminalCleanup = false)
|
||||
{
|
||||
if (preferHybridTerminalCleanup)
|
||||
{
|
||||
return ApplyLeanHybridBaselinePostProcessing(edges, nodes, layoutOptions);
|
||||
}
|
||||
|
||||
var result = ElkEdgePostProcessor.AvoidNodeCrossings(edges, nodes, layoutOptions.Direction);
|
||||
result = ElkEdgePostProcessor.EliminateDiagonalSegments(result, nodes);
|
||||
result = ElkEdgePostProcessorSimplify.SimplifyEdgePaths(result, nodes);
|
||||
@@ -143,11 +149,17 @@ internal static partial class ElkEdgeRouterIterative
|
||||
var retryState = BuildRetryState(score, remainingBrokenHighways);
|
||||
if (retryState.RequiresBlockingRetry || retryState.RequiresLengthRetry)
|
||||
{
|
||||
var stabilized = ApplyTerminalRuleCleanupRound(
|
||||
result,
|
||||
nodes,
|
||||
layoutOptions.Direction,
|
||||
minLineClearance);
|
||||
var stabilized = preferHybridTerminalCleanup
|
||||
? ApplyHybridTerminalRuleCleanupRound(
|
||||
result,
|
||||
nodes,
|
||||
layoutOptions.Direction,
|
||||
minLineClearance)
|
||||
: ApplyTerminalRuleCleanupRound(
|
||||
result,
|
||||
nodes,
|
||||
layoutOptions.Direction,
|
||||
minLineClearance);
|
||||
var stabilizedScore = ElkEdgeRoutingScoring.ComputeScore(stabilized, nodes);
|
||||
var stabilizedBrokenHighways = HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(stabilized, nodes).Count
|
||||
@@ -165,760 +177,4 @@ internal static partial class ElkEdgeRouterIterative
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ElkRoutedEdge[] ApplyTerminalRuleCleanupRound(
|
||||
ElkRoutedEdge[] edges,
|
||||
ElkPositionedNode[] nodes,
|
||||
ElkLayoutDirection direction,
|
||||
double minLineClearance,
|
||||
IReadOnlyCollection<string>? restrictedEdgeIds = null)
|
||||
{
|
||||
var result = edges;
|
||||
result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SeparateRepeatCollectorLocalLaneConflicts(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(result, nodes, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.ElevateRepeatCollectorNodeClearanceViolations(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkRepeatCollectorCorridors.SeparateSharedLanes(result, nodes, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.FinalizeDecisionTargetEntries(result, nodes, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes, restrictedEdgeIds);
|
||||
result = ClampBelowGraphEdges(result, nodes, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, direction, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes, restrictedEdgeIds);
|
||||
result = ClampBelowGraphEdges(result, nodes, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, direction, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes);
|
||||
// Final late-stage verification: source/target boundary normalization can collapse
|
||||
// lanes back onto the same node face, so restabilize the local geometry once more.
|
||||
result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ClampBelowGraphEdges(result, nodes, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, direction, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.ElevateRepeatCollectorNodeClearanceViolations(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.ElevateUnderNodeViolations(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(result, nodes, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.ElevateUnderNodeViolations(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes, restrictedEdgeIds);
|
||||
result = ClampBelowGraphEdges(result, nodes, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, direction, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.ElevateUnderNodeViolations(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes);
|
||||
// Final hard-rule restabilization after the last normalize pass: the final
|
||||
// boundary normalization can still pull target slots and horizontal lanes back
|
||||
// into a bad state, so re-apply the local rule fixers once more before scoring.
|
||||
result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes);
|
||||
result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.ElevateRepeatCollectorNodeClearanceViolations(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.ElevateUnderNodeViolations(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ClampBelowGraphEdges(result, nodes, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, direction, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes);
|
||||
result = CloseRemainingTerminalViolations(result, nodes, direction, minLineClearance, restrictedEdgeIds);
|
||||
var lateDetourShortcuts = ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(result, nodes, restrictedEdgeIds);
|
||||
result = ElkEdgeRoutingScoring.CountBoundarySlotViolations(result, nodes) > 0
|
||||
? ChoosePreferredBoundarySlotRepairLayout(result, lateDetourShortcuts, nodes)
|
||||
: ChoosePreferredHardRuleLayout(result, lateDetourShortcuts, nodes);
|
||||
result = ApplyFinalDetourPolish(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
||||
result,
|
||||
nodes,
|
||||
minLineClearance,
|
||||
restrictedEdgeIds,
|
||||
enforceAllNodeEndpoints: true);
|
||||
result = ApplyPostSlotDetourClosure(result, nodes, minLineClearance, restrictedEdgeIds);
|
||||
result = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
||||
result,
|
||||
nodes,
|
||||
minLineClearance,
|
||||
restrictedEdgeIds,
|
||||
enforceAllNodeEndpoints: true);
|
||||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static ElkRoutedEdge[] CloseRemainingTerminalViolations(
|
||||
ElkRoutedEdge[] edges,
|
||||
ElkPositionedNode[] nodes,
|
||||
ElkLayoutDirection direction,
|
||||
double minLineClearance,
|
||||
IReadOnlyCollection<string>? restrictedEdgeIds)
|
||||
{
|
||||
var result = edges;
|
||||
var restrictedSet = restrictedEdgeIds is null
|
||||
? null
|
||||
: restrictedEdgeIds.ToHashSet(StringComparer.Ordinal);
|
||||
ElkLayoutDiagnostics.LogProgress(
|
||||
$"Terminal closure start: restricted={restrictedEdgeIds?.Count ?? 0}");
|
||||
|
||||
for (var round = 0; round < 4; round++)
|
||||
{
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} pressure scan start");
|
||||
var severityByEdgeId = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
var previousHardPressure =
|
||||
ElkEdgeRoutingScoring.CountBadBoundaryAngles(result, nodes, severityByEdgeId, 10)
|
||||
+ ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(result, nodes, severityByEdgeId, 10)
|
||||
+ ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(result, nodes, severityByEdgeId, 10)
|
||||
+ ElkEdgeRoutingScoring.CountSharedLaneViolations(result, nodes, severityByEdgeId, 10)
|
||||
+ ElkEdgeRoutingScoring.CountBoundarySlotViolations(result, nodes, severityByEdgeId, 10)
|
||||
+ ElkEdgeRoutingScoring.CountBelowGraphViolations(result, nodes, severityByEdgeId, 10)
|
||||
+ ElkEdgeRoutingScoring.CountUnderNodeViolations(result, nodes, severityByEdgeId, 10);
|
||||
var previousLengthPressure = 0;
|
||||
if (previousHardPressure == 0)
|
||||
{
|
||||
previousLengthPressure =
|
||||
ElkEdgeRoutingScoring.CountTargetApproachBacktrackingViolations(result, nodes, severityByEdgeId, 10)
|
||||
+ ElkEdgeRoutingScoring.CountExcessiveDetourViolations(result, nodes, severityByEdgeId, 10);
|
||||
}
|
||||
ElkLayoutDiagnostics.LogProgress(
|
||||
$"Terminal closure round {round + 1} pressure scan done: hard={previousHardPressure} length={previousLengthPressure} severity={severityByEdgeId.Count}");
|
||||
|
||||
var previousScore = ElkEdgeRoutingScoring.ComputeScore(result, nodes);
|
||||
var previousRetryState = BuildRetryState(
|
||||
previousScore,
|
||||
HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(result, nodes).Count
|
||||
: 0);
|
||||
ElkLayoutDiagnostics.LogProgress(
|
||||
$"Terminal closure round {round + 1} retry state ready: {DescribeRetryState(previousRetryState)}");
|
||||
if (previousHardPressure == 0 && previousLengthPressure == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var focusEdgeIds = severityByEdgeId.Keys
|
||||
.Where(edgeId => restrictedSet is null || restrictedSet.Contains(edgeId))
|
||||
.OrderBy(edgeId => edgeId, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
if (focusEdgeIds.Length == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
ElkLayoutDiagnostics.LogProgress(
|
||||
$"Terminal closure round {round + 1} focus ready: count={focusEdgeIds.Length}");
|
||||
|
||||
var focused = (IReadOnlyCollection<string>)focusEdgeIds;
|
||||
var candidate = result;
|
||||
if (previousHardPressure > 0
|
||||
&& ShouldPreferCompactFocusedTerminalClosure(previousRetryState, focusEdgeIds.Length))
|
||||
{
|
||||
ElkLayoutDiagnostics.LogProgress(
|
||||
$"Terminal closure round {round + 1} compact hard-pass start");
|
||||
candidate = ApplyCompactFocusedTerminalClosure(
|
||||
candidate,
|
||||
nodes,
|
||||
direction,
|
||||
minLineClearance,
|
||||
focused);
|
||||
ElkLayoutDiagnostics.LogProgress(
|
||||
$"Terminal closure round {round + 1} compact hard-pass complete");
|
||||
}
|
||||
else if (previousHardPressure > 0)
|
||||
{
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} hard-pass block start");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.NormalizeBoundaryAngles(current, nodes));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after normalize-boundary");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.NormalizeSourceExitAngles(current, nodes));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after normalize-source-exit");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SeparateSharedLaneConflicts(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after separate-shared-lanes-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SeparateRepeatCollectorLocalLaneConflicts(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after separate-repeat-collector-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkRepeatCollectorCorridors.SeparateSharedLanes(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after repeat-collector-shared-lanes-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SpreadSourceDepartureJoins(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after spread-source-joins-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after separate-mixed-faces-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.ElevateUnderNodeViolations(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after elevate-under-node-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.ElevateRepeatCollectorNodeClearanceViolations(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after elevate-repeat-collector-clearance-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after prefer-shortest-shortcuts-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ClampBelowGraphEdges(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after clamp-below-graph-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after repair-boundary-target-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SpreadTargetApproachJoins(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after spread-target-joins-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after spread-feeder-bands-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after finalize-gateway-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.AvoidNodeCrossings(current, nodes, direction, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after avoid-node-crossings-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.ElevateUnderNodeViolations(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after elevate-under-node-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.NormalizeBoundaryAngles(current, nodes));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after normalize-boundary-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.NormalizeSourceExitAngles(current, nodes));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after normalize-source-exit-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SeparateSharedLaneConflicts(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after separate-shared-lanes-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SeparateRepeatCollectorLocalLaneConflicts(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after separate-repeat-collector-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkRepeatCollectorCorridors.SeparateSharedLanes(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after repeat-collector-shared-lanes-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.NormalizeBoundaryAngles(current, nodes));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after normalize-boundary-3");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.NormalizeSourceExitAngles(current, nodes));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after normalize-source-exit-3");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SpreadSourceDepartureJoins(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after spread-source-joins-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after separate-mixed-faces-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after repair-boundary-target-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SpreadTargetApproachJoins(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after spread-target-joins-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after spread-feeder-bands-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ClampBelowGraphEdges(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after clamp-below-graph-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.AvoidNodeCrossings(current, nodes, direction, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after avoid-node-crossings-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.ElevateUnderNodeViolations(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after elevate-under-node-3");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.NormalizeBoundaryAngles(current, nodes));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after normalize-boundary-4");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.NormalizeSourceExitAngles(current, nodes));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after normalize-source-exit-4");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SpreadSourceDepartureJoins(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after spread-source-joins-3");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after separate-mixed-faces-3");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SeparateSharedLaneConflicts(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after separate-shared-lanes-3");
|
||||
}
|
||||
else
|
||||
{
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} length-pass block start");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after prefer-shortest-shortcuts-length-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after repair-boundary-target-length-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SpreadTargetApproachJoins(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after spread-target-joins-length-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after spread-feeder-bands-length-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.FinalizeDecisionTargetEntries(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after finalize-decision-targets-length-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after finalize-gateway-length-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ClampBelowGraphEdges(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after clamp-below-graph-length-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.AvoidNodeCrossings(current, nodes, direction, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after avoid-node-crossings-length-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.NormalizeBoundaryAngles(current, nodes));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after normalize-boundary-length-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.NormalizeSourceExitAngles(current, nodes));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after normalize-source-exit-length-1");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after prefer-shortest-shortcuts-length-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after repair-boundary-target-length-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.SpreadTargetApproachJoins(current, nodes, minLineClearance, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after spread-target-joins-length-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.FinalizeDecisionTargetEntries(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after finalize-decision-targets-length-2");
|
||||
candidate = ApplyGuardedFocusedHardRulePass(candidate, nodes, current => ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(current, nodes, focused));
|
||||
ElkLayoutDiagnostics.LogProgress($"Terminal closure round {round + 1} after finalize-gateway-length-2");
|
||||
}
|
||||
|
||||
var currentHardPressure =
|
||||
ElkEdgeRoutingScoring.CountBadBoundaryAngles(candidate, nodes)
|
||||
+ ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(candidate, nodes)
|
||||
+ ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(candidate, nodes)
|
||||
+ ElkEdgeRoutingScoring.CountSharedLaneViolations(candidate, nodes)
|
||||
+ ElkEdgeRoutingScoring.CountBoundarySlotViolations(candidate, nodes)
|
||||
+ ElkEdgeRoutingScoring.CountBelowGraphViolations(candidate, nodes)
|
||||
+ ElkEdgeRoutingScoring.CountUnderNodeViolations(candidate, nodes);
|
||||
var currentLengthPressure =
|
||||
ElkEdgeRoutingScoring.CountTargetApproachBacktrackingViolations(candidate, nodes)
|
||||
+ ElkEdgeRoutingScoring.CountExcessiveDetourViolations(candidate, nodes);
|
||||
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidate, nodes);
|
||||
var candidateRetryState = BuildRetryState(
|
||||
candidateScore,
|
||||
HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidate, nodes).Count
|
||||
: 0);
|
||||
var improvedBoundarySlots = candidateRetryState.BoundarySlotViolations < previousRetryState.BoundarySlotViolations;
|
||||
var rejectedByRegression = improvedBoundarySlots
|
||||
? candidateScore.NodeCrossings > previousScore.NodeCrossings
|
||||
|| HasBlockingBoundarySlotPromotionRegression(candidateRetryState, previousRetryState)
|
||||
: HasHardRuleRegression(candidateRetryState, previousRetryState);
|
||||
var madeProgress = improvedBoundarySlots
|
||||
|| (previousHardPressure > 0
|
||||
? currentHardPressure < previousHardPressure
|
||||
: currentLengthPressure < previousLengthPressure);
|
||||
if (rejectedByRegression || !madeProgress)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
result = candidate;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ElkRoutedEdge[] ApplyGuardedFocusedHardRulePass(
|
||||
ElkRoutedEdge[] current,
|
||||
ElkPositionedNode[] nodes,
|
||||
Func<ElkRoutedEdge[], ElkRoutedEdge[]> pass)
|
||||
{
|
||||
var candidate = pass(current);
|
||||
return ElkEdgeRoutingScoring.CountBoundarySlotViolations(current, nodes) > 0
|
||||
? ChoosePreferredBoundarySlotRepairLayout(current, candidate, nodes)
|
||||
: ChoosePreferredHardRuleLayout(current, candidate, nodes);
|
||||
}
|
||||
|
||||
private static ElkRoutedEdge[] ChoosePreferredBoundarySlotRepairLayout(
|
||||
ElkRoutedEdge[] baseline,
|
||||
ElkRoutedEdge[] candidate,
|
||||
ElkPositionedNode[] nodes)
|
||||
{
|
||||
if (ReferenceEquals(candidate, baseline))
|
||||
{
|
||||
return baseline;
|
||||
}
|
||||
|
||||
var baselineScore = ElkEdgeRoutingScoring.ComputeScore(baseline, nodes);
|
||||
var baselineRetryState = BuildRetryState(
|
||||
baselineScore,
|
||||
HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(baseline, nodes).Count
|
||||
: 0);
|
||||
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidate, nodes);
|
||||
var candidateRetryState = BuildRetryState(
|
||||
candidateScore,
|
||||
HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidate, nodes).Count
|
||||
: 0);
|
||||
|
||||
if (!IsBetterBoundarySlotRepairCandidate(
|
||||
candidateScore,
|
||||
candidateRetryState,
|
||||
baselineScore,
|
||||
baselineRetryState))
|
||||
{
|
||||
return baseline;
|
||||
}
|
||||
|
||||
// Boundary-slot repair is staged ahead of other soft cleanups. Once a
|
||||
// candidate legitimately reduces boundary-slot violations without
|
||||
// introducing a blocking hard regression, keep it alive so the later
|
||||
// shared-lane / detour passes can recover any temporary soft tradeoff.
|
||||
if (candidateRetryState.BoundarySlotViolations < baselineRetryState.BoundarySlotViolations)
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
|
||||
var retryComparison = CompareRetryStates(candidateRetryState, baselineRetryState);
|
||||
if (retryComparison < 0)
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
|
||||
if (retryComparison > 0)
|
||||
{
|
||||
return baseline;
|
||||
}
|
||||
|
||||
if (candidateScore.NodeCrossings != baselineScore.NodeCrossings)
|
||||
{
|
||||
return candidateScore.NodeCrossings < baselineScore.NodeCrossings
|
||||
? candidate
|
||||
: baseline;
|
||||
}
|
||||
|
||||
return candidateScore.Value > baselineScore.Value
|
||||
? candidate
|
||||
: baseline;
|
||||
}
|
||||
|
||||
private static ElkRoutedEdge[] ChoosePreferredSharedLanePolishLayout(
|
||||
ElkRoutedEdge[] baseline,
|
||||
ElkRoutedEdge[] candidate,
|
||||
ElkPositionedNode[] nodes)
|
||||
{
|
||||
if (ReferenceEquals(candidate, baseline))
|
||||
{
|
||||
return baseline;
|
||||
}
|
||||
|
||||
var baselineScore = ElkEdgeRoutingScoring.ComputeScore(baseline, nodes);
|
||||
var baselineRetryState = BuildRetryState(
|
||||
baselineScore,
|
||||
HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(baseline, nodes).Count
|
||||
: 0);
|
||||
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidate, nodes);
|
||||
var candidateRetryState = BuildRetryState(
|
||||
candidateScore,
|
||||
HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidate, nodes).Count
|
||||
: 0);
|
||||
|
||||
if (!IsBetterSharedLanePolishCandidate(
|
||||
candidateScore,
|
||||
candidateRetryState,
|
||||
baselineScore,
|
||||
baselineRetryState))
|
||||
{
|
||||
return baseline;
|
||||
}
|
||||
|
||||
if (candidateRetryState.SharedLaneViolations < baselineRetryState.SharedLaneViolations)
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
|
||||
var retryComparison = CompareRetryStates(candidateRetryState, baselineRetryState);
|
||||
if (retryComparison < 0)
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
|
||||
if (retryComparison > 0)
|
||||
{
|
||||
return baseline;
|
||||
}
|
||||
|
||||
if (candidateScore.NodeCrossings != baselineScore.NodeCrossings)
|
||||
{
|
||||
return candidateScore.NodeCrossings < baselineScore.NodeCrossings
|
||||
? candidate
|
||||
: baseline;
|
||||
}
|
||||
|
||||
return candidateScore.Value > baselineScore.Value
|
||||
? candidate
|
||||
: baseline;
|
||||
}
|
||||
|
||||
private static bool IsBetterBoundarySlotRepairCandidate(
|
||||
EdgeRoutingScore candidateScore,
|
||||
RoutingRetryState candidateRetryState,
|
||||
EdgeRoutingScore baselineScore,
|
||||
RoutingRetryState baselineRetryState)
|
||||
{
|
||||
if (candidateRetryState.BoundarySlotViolations < baselineRetryState.BoundarySlotViolations)
|
||||
{
|
||||
return candidateScore.NodeCrossings <= baselineScore.NodeCrossings
|
||||
&& !HasBlockingBoundarySlotPromotionRegression(candidateRetryState, baselineRetryState);
|
||||
}
|
||||
|
||||
return IsBetterCandidate(candidateScore, candidateRetryState, baselineScore, baselineRetryState);
|
||||
}
|
||||
|
||||
private static bool IsBetterSharedLanePolishCandidate(
|
||||
EdgeRoutingScore candidateScore,
|
||||
RoutingRetryState candidateRetryState,
|
||||
EdgeRoutingScore baselineScore,
|
||||
RoutingRetryState baselineRetryState)
|
||||
{
|
||||
if (candidateRetryState.SharedLaneViolations < baselineRetryState.SharedLaneViolations)
|
||||
{
|
||||
return candidateScore.NodeCrossings <= baselineScore.NodeCrossings
|
||||
&& !HasBlockingSharedLanePromotionRegression(candidateRetryState, baselineRetryState);
|
||||
}
|
||||
|
||||
return IsBetterCandidate(candidateScore, candidateRetryState, baselineScore, baselineRetryState);
|
||||
}
|
||||
|
||||
private static ElkRoutedEdge[] ChoosePreferredHardRuleLayout(
|
||||
ElkRoutedEdge[] baseline,
|
||||
ElkRoutedEdge[] candidate,
|
||||
ElkPositionedNode[] nodes)
|
||||
{
|
||||
if (ReferenceEquals(candidate, baseline))
|
||||
{
|
||||
return baseline;
|
||||
}
|
||||
|
||||
var baselineScore = ElkEdgeRoutingScoring.ComputeScore(baseline, nodes);
|
||||
var baselineRetryState = BuildRetryState(
|
||||
baselineScore,
|
||||
HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(baseline, nodes).Count
|
||||
: 0);
|
||||
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidate, nodes);
|
||||
var candidateRetryState = BuildRetryState(
|
||||
candidateScore,
|
||||
HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidate, nodes).Count
|
||||
: 0);
|
||||
|
||||
if (HasHardRuleRegression(candidateRetryState, baselineRetryState))
|
||||
{
|
||||
return baseline;
|
||||
}
|
||||
|
||||
var retryComparison = CompareRetryStates(candidateRetryState, baselineRetryState);
|
||||
if (retryComparison < 0)
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
|
||||
if (retryComparison > 0)
|
||||
{
|
||||
return baseline;
|
||||
}
|
||||
|
||||
if (candidateScore.NodeCrossings != baselineScore.NodeCrossings)
|
||||
{
|
||||
return candidateScore.NodeCrossings < baselineScore.NodeCrossings
|
||||
? candidate
|
||||
: baseline;
|
||||
}
|
||||
|
||||
return candidateScore.Value > baselineScore.Value
|
||||
? candidate
|
||||
: baseline;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user