1665 lines
71 KiB
C#
1665 lines
71 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
|
|
namespace StellaOps.ElkSharp;
|
|
|
|
internal static partial class ElkEdgeRouterIterative
|
|
{
|
|
private static CandidateSolution RefineWinningSolution(
|
|
CandidateSolution best,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance)
|
|
{
|
|
static string DescribeSolution(CandidateSolution solution)
|
|
{
|
|
return $"score={solution.Score.Value:F0} retry={DescribeRetryState(solution.RetryState)}";
|
|
}
|
|
|
|
var current = best;
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement start: {DescribeSolution(current)}");
|
|
for (var round = 0; round < 3; round++)
|
|
{
|
|
var severityByEdgeId = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
var pressure =
|
|
ElkEdgeRoutingScoring.CountBadBoundaryAngles(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountSharedLaneViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountBoundarySlotViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountBelowGraphViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountUnderNodeViolations(current.Edges, nodes, severityByEdgeId, 10);
|
|
if (pressure == 0)
|
|
{
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement pressure clear after round {round + 1}: {DescribeSolution(current)}");
|
|
break;
|
|
}
|
|
|
|
var improved = false;
|
|
foreach (var focusRootEdgeId in severityByEdgeId
|
|
.OrderByDescending(pair => pair.Value)
|
|
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
|
.Select(pair => pair.Key))
|
|
{
|
|
var focusEdgeIds = ExpandWinningSolutionFocus(current.Edges, [focusRootEdgeId]).ToArray();
|
|
if (focusEdgeIds.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var candidateEdges = CloseRemainingTerminalViolations(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
|
|
: 0);
|
|
|
|
if (!IsBetterBoundarySlotRepairCandidate(
|
|
candidateScore,
|
|
candidateRetryState,
|
|
current.Score,
|
|
current.RetryState))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
current = current with
|
|
{
|
|
Score = candidateScore,
|
|
RetryState = candidateRetryState,
|
|
Edges = candidateEdges,
|
|
};
|
|
improved = true;
|
|
break;
|
|
}
|
|
|
|
if (!improved)
|
|
{
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement pressure round {round + 1} made no improvement: {DescribeSolution(current)}");
|
|
break;
|
|
}
|
|
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement pressure round {round + 1} improved: {DescribeSolution(current)}");
|
|
}
|
|
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement before under-node polish: {DescribeSolution(current)}");
|
|
current = ApplyFinalDirectUnderNodePolish(current, nodes, minLineClearance);
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement after under-node polish: {DescribeSolution(current)}");
|
|
current = ApplyFinalProtectedLocalBundlePolish(current, nodes, minLineClearance);
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement after local-bundle polish: {DescribeSolution(current)}");
|
|
current = ApplyFinalSharedLanePolish(current, nodes, direction, minLineClearance);
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement after shared-lane polish: {DescribeSolution(current)}");
|
|
current = ApplyFinalBoundarySlotPolish(current, nodes, direction, minLineClearance);
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement after boundary-slot polish: {DescribeSolution(current)}");
|
|
current = ApplyWinnerDetourPolish(current, nodes, minLineClearance);
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement after detour polish: {DescribeSolution(current)}");
|
|
current = ApplyFinalPostSlotHardRulePolish(current, nodes, direction, minLineClearance);
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement after post-slot hard-rule polish: {DescribeSolution(current)}");
|
|
if (current.RetryState.SharedLaneViolations == 0
|
|
&& (current.RetryState.BoundarySlotViolations > 0
|
|
|| current.RetryState.ExcessiveDetourViolations > 0))
|
|
{
|
|
current = ApplyFinalBoundarySlotPolish(current, nodes, direction, minLineClearance);
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement after second boundary-slot polish: {DescribeSolution(current)}");
|
|
current = ApplyWinnerDetourPolish(current, nodes, minLineClearance);
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement after second detour polish: {DescribeSolution(current)}");
|
|
current = ApplyFinalPostSlotHardRulePolish(current, nodes, direction, minLineClearance);
|
|
ElkLayoutDiagnostics.LogProgress($"Winner refinement after second post-slot hard-rule polish: {DescribeSolution(current)}");
|
|
}
|
|
return current;
|
|
}
|
|
|
|
private static IEnumerable<string> ExpandWinningSolutionFocus(
|
|
IReadOnlyCollection<ElkRoutedEdge> edges,
|
|
IEnumerable<string> focusEdgeIds)
|
|
{
|
|
var edgesById = edges.ToDictionary(edge => edge.Id, StringComparer.Ordinal);
|
|
var expanded = new HashSet<string>(StringComparer.Ordinal);
|
|
|
|
foreach (var edgeId in focusEdgeIds)
|
|
{
|
|
if (!expanded.Add(edgeId) || !edgesById.TryGetValue(edgeId, out var edge))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var peer in edges)
|
|
{
|
|
if (string.Equals(peer.Id, edge.Id, StringComparison.Ordinal))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (string.Equals(peer.SourceNodeId, edge.SourceNodeId, StringComparison.Ordinal)
|
|
|| string.Equals(peer.TargetNodeId, edge.TargetNodeId, StringComparison.Ordinal)
|
|
|| string.Equals(peer.SourceNodeId, edge.TargetNodeId, StringComparison.Ordinal)
|
|
|| string.Equals(peer.TargetNodeId, edge.SourceNodeId, StringComparison.Ordinal))
|
|
{
|
|
expanded.Add(peer.Id);
|
|
}
|
|
}
|
|
}
|
|
|
|
return expanded.OrderBy(edgeId => edgeId, StringComparer.Ordinal);
|
|
}
|
|
|
|
private static IEnumerable<string> ExpandSharedLanePolishFocus(
|
|
IReadOnlyCollection<ElkRoutedEdge> edges,
|
|
IReadOnlyCollection<ElkPositionedNode> nodes,
|
|
string focusEdgeId)
|
|
{
|
|
var edgesById = edges.ToDictionary(edge => edge.Id, StringComparer.Ordinal);
|
|
if (!edgesById.TryGetValue(focusEdgeId, out var focusEdge))
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var focusedEdgeIds = new HashSet<string>(StringComparer.Ordinal)
|
|
{
|
|
focusEdgeId,
|
|
};
|
|
var sharedNodeIds = new HashSet<string>(StringComparer.Ordinal);
|
|
|
|
foreach (var (leftEdgeId, rightEdgeId) in ElkEdgeRoutingScoring.DetectSharedLaneConflicts(edges, nodes))
|
|
{
|
|
string partnerEdgeId;
|
|
if (string.Equals(leftEdgeId, focusEdgeId, StringComparison.Ordinal))
|
|
{
|
|
partnerEdgeId = rightEdgeId;
|
|
}
|
|
else if (string.Equals(rightEdgeId, focusEdgeId, StringComparison.Ordinal))
|
|
{
|
|
partnerEdgeId = leftEdgeId;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!edgesById.TryGetValue(partnerEdgeId, out var partnerEdge))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
focusedEdgeIds.Add(partnerEdgeId);
|
|
CollectSharedConflictNodeIds(focusEdge, partnerEdge, sharedNodeIds);
|
|
}
|
|
|
|
if (sharedNodeIds.Count == 0)
|
|
{
|
|
return ExpandWinningSolutionFocus(edges, [focusEdgeId]);
|
|
}
|
|
|
|
foreach (var edge in edges)
|
|
{
|
|
if (focusedEdgeIds.Contains(edge.Id))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((edge.SourceNodeId is not null && sharedNodeIds.Contains(edge.SourceNodeId))
|
|
|| (edge.TargetNodeId is not null && sharedNodeIds.Contains(edge.TargetNodeId)))
|
|
{
|
|
focusedEdgeIds.Add(edge.Id);
|
|
}
|
|
}
|
|
|
|
return focusedEdgeIds.OrderBy(edgeId => edgeId, StringComparer.Ordinal);
|
|
}
|
|
|
|
private static void CollectSharedConflictNodeIds(
|
|
ElkRoutedEdge edge,
|
|
ElkRoutedEdge partner,
|
|
ISet<string> sharedNodeIds)
|
|
{
|
|
if (edge.SourceNodeId is not null
|
|
&& (string.Equals(edge.SourceNodeId, partner.SourceNodeId, StringComparison.Ordinal)
|
|
|| string.Equals(edge.SourceNodeId, partner.TargetNodeId, StringComparison.Ordinal)))
|
|
{
|
|
sharedNodeIds.Add(edge.SourceNodeId);
|
|
}
|
|
|
|
if (edge.TargetNodeId is not null
|
|
&& (string.Equals(edge.TargetNodeId, partner.SourceNodeId, StringComparison.Ordinal)
|
|
|| string.Equals(edge.TargetNodeId, partner.TargetNodeId, StringComparison.Ordinal)))
|
|
{
|
|
sharedNodeIds.Add(edge.TargetNodeId);
|
|
}
|
|
}
|
|
|
|
private static CandidateSolution ApplyFinalDirectUnderNodePolish(
|
|
CandidateSolution solution,
|
|
ElkPositionedNode[] nodes,
|
|
double minLineClearance)
|
|
{
|
|
var current = solution;
|
|
var underNodeSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
ElkEdgeRoutingScoring.CountUnderNodeViolations(current.Edges, nodes, underNodeSeverity, 10);
|
|
if (underNodeSeverity.Count == 0)
|
|
{
|
|
return current;
|
|
}
|
|
|
|
foreach (var edgeId in underNodeSeverity
|
|
.OrderByDescending(pair => pair.Value)
|
|
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
|
.Select(pair => pair.Key))
|
|
{
|
|
var candidateEdges = ElkEdgePostProcessor.ElevateUnderNodeViolations(
|
|
current.Edges,
|
|
nodes,
|
|
minLineClearance,
|
|
[edgeId]);
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
|
|
: 0);
|
|
if (!IsBetterCandidate(candidateScore, candidateRetryState, current.Score, current.RetryState))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
current = current with
|
|
{
|
|
Score = candidateScore,
|
|
RetryState = candidateRetryState,
|
|
Edges = candidateEdges,
|
|
};
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
private static CandidateSolution ApplyFinalProtectedLocalBundlePolish(
|
|
CandidateSolution solution,
|
|
ElkPositionedNode[] nodes,
|
|
double minLineClearance)
|
|
{
|
|
var current = solution;
|
|
var underNodeSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
ElkEdgeRoutingScoring.CountUnderNodeViolations(current.Edges, nodes, underNodeSeverity, 10);
|
|
|
|
var focusSeverity = new Dictionary<string, int>(underNodeSeverity, StringComparer.Ordinal);
|
|
ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(current.Edges, nodes, focusSeverity, 10);
|
|
if (focusSeverity.Count == 0)
|
|
{
|
|
return current;
|
|
}
|
|
|
|
foreach (var edgeId in focusSeverity
|
|
.OrderByDescending(pair => pair.Value)
|
|
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
|
.Select(pair => pair.Key))
|
|
{
|
|
var focusEdgeIds = ExpandWinningSolutionFocus(current.Edges, [edgeId]).ToArray();
|
|
if (focusEdgeIds.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var focusedRepairSeeds = focusEdgeIds
|
|
.Where(underNodeSeverity.ContainsKey)
|
|
.OrderBy(id => id, StringComparer.Ordinal)
|
|
.ToArray();
|
|
if (focusedRepairSeeds.Length == 0)
|
|
{
|
|
focusedRepairSeeds = [edgeId];
|
|
}
|
|
|
|
var candidateEdges = ElkEdgePostProcessor.ElevateUnderNodeViolations(
|
|
current.Edges,
|
|
nodes,
|
|
minLineClearance,
|
|
focusedRepairSeeds);
|
|
candidateEdges = ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
|
candidateEdges,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds,
|
|
forceOutwardAxisSpacing: true);
|
|
candidateEdges = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(
|
|
candidateEdges,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
candidateEdges = ElkEdgePostProcessor.SeparateSharedLaneConflicts(
|
|
candidateEdges,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
candidateEdges = ElkEdgePostProcessor.ElevateUnderNodeViolations(
|
|
candidateEdges,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
candidateEdges = ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
|
candidateEdges,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds,
|
|
forceOutwardAxisSpacing: true);
|
|
candidateEdges = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(
|
|
candidateEdges,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
candidateEdges = ChoosePreferredHardRuleLayout(current.Edges, candidateEdges, nodes);
|
|
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
|
|
: 0);
|
|
if (!IsBetterCandidate(candidateScore, candidateRetryState, current.Score, current.RetryState))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
current = current with
|
|
{
|
|
Score = candidateScore,
|
|
RetryState = candidateRetryState,
|
|
Edges = candidateEdges,
|
|
};
|
|
|
|
underNodeSeverity.Clear();
|
|
ElkEdgeRoutingScoring.CountUnderNodeViolations(current.Edges, nodes, underNodeSeverity, 10);
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
private static CandidateSolution ApplyFinalSharedLanePolish(
|
|
CandidateSolution solution,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance)
|
|
{
|
|
var current = solution;
|
|
if (current.RetryState.SharedLaneViolations <= 0)
|
|
{
|
|
return current;
|
|
}
|
|
|
|
for (var round = 0; round < 3; round++)
|
|
{
|
|
var sharedLaneSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
ElkEdgeRoutingScoring.CountSharedLaneViolations(current.Edges, nodes, sharedLaneSeverity, 10);
|
|
if (sharedLaneSeverity.Count == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
var improved = false;
|
|
foreach (var edgeId in sharedLaneSeverity
|
|
.OrderByDescending(pair => pair.Value)
|
|
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
|
.Select(pair => pair.Key))
|
|
{
|
|
var focusEdgeIds = ExpandSharedLanePolishFocus(current.Edges, nodes, edgeId).ToArray();
|
|
if (focusEdgeIds.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var directCandidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts(
|
|
current.Edges,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
var directClosureCandidate = CloseRemainingTerminalViolations(
|
|
directCandidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
var closureCandidate = CloseRemainingTerminalViolations(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
var aggressiveCandidate = ApplyAggressiveSharedLaneClosure(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
var directRetryState = BuildRetryState(
|
|
ElkEdgeRoutingScoring.ComputeScore(directCandidate, nodes),
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(directCandidate, nodes).Count
|
|
: 0);
|
|
var directClosureRetryState = BuildRetryState(
|
|
ElkEdgeRoutingScoring.ComputeScore(directClosureCandidate, nodes),
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(directClosureCandidate, nodes).Count
|
|
: 0);
|
|
var closureRetryState = BuildRetryState(
|
|
ElkEdgeRoutingScoring.ComputeScore(closureCandidate, nodes),
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(closureCandidate, nodes).Count
|
|
: 0);
|
|
var aggressiveRetryState = BuildRetryState(
|
|
ElkEdgeRoutingScoring.ComputeScore(aggressiveCandidate, nodes),
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(aggressiveCandidate, nodes).Count
|
|
: 0);
|
|
ElkLayoutDiagnostics.LogProgress(
|
|
$"Winner shared-lane focus edge={edgeId} focus=[{string.Join(", ", focusEdgeIds)}] " +
|
|
$"direct={DescribeRetryState(directRetryState)} " +
|
|
$"direct-closure={DescribeRetryState(directClosureRetryState)} " +
|
|
$"closure={DescribeRetryState(closureRetryState)} " +
|
|
$"aggressive={DescribeRetryState(aggressiveRetryState)}");
|
|
var candidateEdges = ChoosePreferredSharedLanePolishLayout(directCandidate, directClosureCandidate, nodes);
|
|
candidateEdges = ChoosePreferredSharedLanePolishLayout(candidateEdges, closureCandidate, nodes);
|
|
candidateEdges = ChoosePreferredSharedLanePolishLayout(candidateEdges, aggressiveCandidate, nodes);
|
|
candidateEdges = ChoosePreferredSharedLanePolishLayout(current.Edges, candidateEdges, nodes);
|
|
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
|
|
: 0);
|
|
|
|
var improvedSharedLanes = candidateRetryState.SharedLaneViolations < current.RetryState.SharedLaneViolations;
|
|
if (HasBlockingSharedLanePromotionRegression(candidateRetryState, current.RetryState)
|
|
|| (!improvedSharedLanes
|
|
&& !IsBetterCandidate(candidateScore, candidateRetryState, current.Score, current.RetryState)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
current = current with
|
|
{
|
|
Score = candidateScore,
|
|
RetryState = candidateRetryState,
|
|
Edges = candidateEdges,
|
|
};
|
|
improved = true;
|
|
break;
|
|
}
|
|
|
|
if (!improved)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
private static CandidateSolution ApplyFinalBoundarySlotPolish(
|
|
CandidateSolution solution,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance)
|
|
{
|
|
var current = solution;
|
|
|
|
for (var round = 0; round < 3; round++)
|
|
{
|
|
var boundarySlotSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
ElkEdgeRoutingScoring.CountBoundarySlotViolations(current.Edges, nodes, boundarySlotSeverity, 10);
|
|
if (boundarySlotSeverity.Count == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
var batchedRootEdgeIds = boundarySlotSeverity
|
|
.OrderByDescending(pair => pair.Value)
|
|
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
|
.Take(MaxWinnerPolishBatchedRootEdges)
|
|
.Select(pair => pair.Key)
|
|
.ToArray();
|
|
var batchedFocusEdgeIds = ExpandWinningSolutionFocus(current.Edges, batchedRootEdgeIds).ToArray();
|
|
if (batchedFocusEdgeIds.Length > 0)
|
|
{
|
|
var batchedCandidateEdges = BuildFinalBoundarySlotCandidate(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
batchedFocusEdgeIds,
|
|
allowLateRestabilizedClosure: false);
|
|
if (TryPromoteFinalBoundarySlotCandidate(current, batchedCandidateEdges, nodes, out var batchedPromoted))
|
|
{
|
|
current = batchedPromoted;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
var improved = false;
|
|
foreach (var edgeId in boundarySlotSeverity
|
|
.OrderByDescending(pair => pair.Value)
|
|
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
|
.Select(pair => pair.Key))
|
|
{
|
|
var focusEdgeIds = ExpandWinningSolutionFocus(current.Edges, [edgeId]).ToArray();
|
|
if (focusEdgeIds.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var candidateEdges = BuildFinalBoundarySlotCandidate(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds,
|
|
allowLateRestabilizedClosure: false);
|
|
|
|
if (!TryPromoteFinalBoundarySlotCandidate(current, candidateEdges, nodes, out var promoted))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
current = promoted;
|
|
improved = true;
|
|
break;
|
|
}
|
|
|
|
if (!improved)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
private static CandidateSolution ApplyFinalPostSlotHardRulePolish(
|
|
CandidateSolution solution,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance)
|
|
{
|
|
var current = solution;
|
|
|
|
for (var round = 0; round < 3; round++)
|
|
{
|
|
var severityByEdgeId = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
var pressure =
|
|
ElkEdgeRoutingScoring.CountBadBoundaryAngles(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountSharedLaneViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountBoundarySlotViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountTargetApproachBacktrackingViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountExcessiveDetourViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountBelowGraphViolations(current.Edges, nodes, severityByEdgeId, 10)
|
|
+ ElkEdgeRoutingScoring.CountUnderNodeViolations(current.Edges, nodes, severityByEdgeId, 10);
|
|
ElkLayoutDiagnostics.LogProgress(
|
|
$"Winner post-slot hard-rule round {round + 1} start: pressure={pressure} retry={DescribeRetryState(current.RetryState)} focus={severityByEdgeId.Count}");
|
|
if (pressure == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
var preferFastTerminalOnly = ShouldPreferFastTerminalOnlyHardRuleClosure(current.RetryState);
|
|
|
|
var batchedRootEdgeIds = severityByEdgeId
|
|
.OrderByDescending(pair => pair.Value)
|
|
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
|
.Take(MaxWinnerPolishBatchedRootEdges)
|
|
.Select(pair => pair.Key)
|
|
.ToArray();
|
|
var batchedFocusEdgeIds = preferFastTerminalOnly
|
|
? batchedRootEdgeIds
|
|
: ExpandWinningSolutionFocus(current.Edges, batchedRootEdgeIds).ToArray();
|
|
if (batchedFocusEdgeIds.Length > 0)
|
|
{
|
|
if (preferFastTerminalOnly)
|
|
{
|
|
ElkLayoutDiagnostics.LogProgress(
|
|
$"Winner post-slot hard-rule round {round + 1} fast-terminal focus=[{string.Join(", ", batchedFocusEdgeIds)}]");
|
|
var quickBatchedCandidateEdges = BuildFastTerminalOnlyHardRuleCandidate(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
batchedFocusEdgeIds);
|
|
if (TryPromoteFinalHardRuleCandidate(current, quickBatchedCandidateEdges, nodes, out var quickBatchedPromoted))
|
|
{
|
|
current = quickBatchedPromoted;
|
|
continue;
|
|
}
|
|
|
|
var focusedTerminalClosureEdges = CloseRemainingTerminalViolations(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
batchedFocusEdgeIds);
|
|
if (TryPromoteFinalHardRuleCandidate(current, focusedTerminalClosureEdges, nodes, out var focusedTerminalPromoted))
|
|
{
|
|
current = focusedTerminalPromoted;
|
|
continue;
|
|
}
|
|
|
|
var exactRestabilizedEdges = BuildFinalRestabilizedCandidate(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
batchedFocusEdgeIds);
|
|
if (TryPromoteFinalHardRuleCandidate(current, exactRestabilizedEdges, nodes, out var exactRestabilizedPromoted))
|
|
{
|
|
current = exactRestabilizedPromoted;
|
|
continue;
|
|
}
|
|
|
|
var quickBatchedScore = ElkEdgeRoutingScoring.ComputeScore(quickBatchedCandidateEdges, nodes);
|
|
var quickBatchedRetryState = BuildRetryState(
|
|
quickBatchedScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(quickBatchedCandidateEdges, nodes).Count
|
|
: 0);
|
|
var changedFocusEdgeIds = batchedFocusEdgeIds
|
|
.Where(edgeId => HasEdgeGeometryChanged(current.Edges, quickBatchedCandidateEdges, edgeId))
|
|
.ToArray();
|
|
ElkLayoutDiagnostics.LogProgress(
|
|
$"Winner post-slot hard-rule round {round + 1} fast-terminal made no promotion: candidate={DescribeRetryState(quickBatchedRetryState)} changed=[{string.Join(", ", changedFocusEdgeIds)}]");
|
|
}
|
|
else
|
|
{
|
|
ElkLayoutDiagnostics.LogProgress(
|
|
$"Winner post-slot hard-rule round {round + 1} full-restabilize focus=[{string.Join(", ", batchedFocusEdgeIds)}]");
|
|
var batchedCandidateEdges = BuildFinalRestabilizedCandidate(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
batchedFocusEdgeIds);
|
|
if (TryPromoteFinalHardRuleCandidate(current, batchedCandidateEdges, nodes, out var batchedPromoted))
|
|
{
|
|
current = batchedPromoted;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
var improved = false;
|
|
foreach (var edgeId in severityByEdgeId
|
|
.OrderByDescending(pair => pair.Value)
|
|
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
|
.Select(pair => pair.Key))
|
|
{
|
|
var focusEdgeIds = preferFastTerminalOnly
|
|
? [edgeId]
|
|
: ExpandWinningSolutionFocus(current.Edges, [edgeId]).ToArray();
|
|
if (focusEdgeIds.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (preferFastTerminalOnly)
|
|
{
|
|
var quickCandidateEdges = BuildFastTerminalOnlyHardRuleCandidate(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
if (TryPromoteFinalHardRuleCandidate(current, quickCandidateEdges, nodes, out var quickPromoted))
|
|
{
|
|
current = quickPromoted;
|
|
improved = true;
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
var candidateEdges = BuildFinalRestabilizedCandidate(
|
|
current.Edges,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
if (!TryPromoteFinalHardRuleCandidate(current, candidateEdges, nodes, out var promoted))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
current = promoted;
|
|
improved = true;
|
|
break;
|
|
}
|
|
|
|
if (!improved)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
private static bool ShouldPreferFastTerminalOnlyHardRuleClosure(RoutingRetryState retryState)
|
|
{
|
|
return retryState.RemainingShortHighways == 0
|
|
&& retryState.RepeatCollectorCorridorViolations == 0
|
|
&& retryState.RepeatCollectorNodeClearanceViolations == 0
|
|
&& retryState.TargetApproachBacktrackingViolations == 0
|
|
&& retryState.ExcessiveDetourViolations == 0
|
|
&& retryState.BoundarySlotViolations == 0
|
|
&& retryState.BelowGraphViolations == 0
|
|
&& retryState.UnderNodeViolations == 0
|
|
&& retryState.LongDiagonalViolations == 0
|
|
&& retryState.SharedLaneViolations <= 2
|
|
&& (retryState.TargetApproachJoinViolations > 0
|
|
|| retryState.EntryAngleViolations > 0
|
|
|| retryState.GatewaySourceExitViolations > 0
|
|
|| retryState.SharedLaneViolations > 0);
|
|
}
|
|
|
|
private static ElkRoutedEdge[] BuildFastTerminalOnlyHardRuleCandidate(
|
|
ElkRoutedEdge[] edges,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance,
|
|
IReadOnlyCollection<string> restrictedEdgeIds)
|
|
{
|
|
var candidate = edges;
|
|
candidate = ChoosePreferredHardRuleLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SeparateSharedLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds),
|
|
nodes);
|
|
candidate = ChoosePreferredHardRuleLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SpreadSourceDepartureJoins(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds),
|
|
nodes);
|
|
candidate = ChoosePreferredHardRuleLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds),
|
|
nodes);
|
|
candidate = ChoosePreferredHardRuleLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
forceOutwardAxisSpacing: true),
|
|
nodes);
|
|
candidate = ChoosePreferredHardRuleLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.NormalizeBoundaryAngles(candidate, nodes),
|
|
nodes);
|
|
candidate = ChoosePreferredHardRuleLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.NormalizeSourceExitAngles(candidate, nodes),
|
|
nodes);
|
|
candidate = ChoosePreferredHardRuleLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SeparateSharedLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds),
|
|
nodes);
|
|
return candidate;
|
|
}
|
|
|
|
private static bool ShouldPreferCompactFocusedTerminalClosure(
|
|
RoutingRetryState retryState,
|
|
int focusEdgeCount)
|
|
{
|
|
return focusEdgeCount <= 4
|
|
&& retryState.BoundarySlotViolations == 0
|
|
&& retryState.BelowGraphViolations == 0
|
|
&& retryState.UnderNodeViolations == 0
|
|
&& retryState.GatewaySourceExitViolations == 0
|
|
&& retryState.EntryAngleViolations == 0
|
|
&& retryState.TargetApproachBacktrackingViolations == 0
|
|
&& retryState.ExcessiveDetourViolations == 0
|
|
&& retryState.TargetApproachJoinViolations <= 1
|
|
&& retryState.SharedLaneViolations <= 1
|
|
&& (retryState.TargetApproachJoinViolations > 0
|
|
|| retryState.SharedLaneViolations > 0);
|
|
}
|
|
|
|
private static ElkRoutedEdge[] ApplyCompactFocusedTerminalClosure(
|
|
ElkRoutedEdge[] edges,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance,
|
|
IReadOnlyCollection<string> focusedEdgeIds)
|
|
{
|
|
var candidate = edges;
|
|
candidate = ApplyGuardedFocusedHardRulePass(
|
|
candidate,
|
|
nodes,
|
|
current => ElkEdgePostProcessor.SeparateSharedLaneConflicts(current, nodes, minLineClearance, focusedEdgeIds));
|
|
candidate = ApplyGuardedFocusedHardRulePass(
|
|
candidate,
|
|
nodes,
|
|
current => ElkEdgePostProcessor.SpreadSourceDepartureJoins(current, nodes, minLineClearance, focusedEdgeIds));
|
|
candidate = ApplyGuardedFocusedHardRulePass(
|
|
candidate,
|
|
nodes,
|
|
current => ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(current, nodes, minLineClearance, focusedEdgeIds));
|
|
candidate = ApplyGuardedFocusedHardRulePass(
|
|
candidate,
|
|
nodes,
|
|
current => ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
|
current,
|
|
nodes,
|
|
minLineClearance,
|
|
focusedEdgeIds,
|
|
forceOutwardAxisSpacing: true));
|
|
candidate = ApplyGuardedFocusedHardRulePass(
|
|
candidate,
|
|
nodes,
|
|
current => ElkEdgePostProcessor.SeparateSharedLaneConflicts(current, nodes, minLineClearance, focusedEdgeIds));
|
|
candidate = ApplyGuardedFocusedHardRulePass(
|
|
candidate,
|
|
nodes,
|
|
current => ElkEdgePostProcessor.NormalizeBoundaryAngles(current, nodes));
|
|
candidate = ApplyGuardedFocusedHardRulePass(
|
|
candidate,
|
|
nodes,
|
|
current => ElkEdgePostProcessor.NormalizeSourceExitAngles(current, nodes));
|
|
return candidate;
|
|
}
|
|
|
|
private static CandidateSolution ApplyWinnerDetourPolish(
|
|
CandidateSolution solution,
|
|
ElkPositionedNode[] nodes,
|
|
double minLineClearance)
|
|
{
|
|
var focusSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
ElkEdgeRoutingScoring.CountExcessiveDetourViolations(solution.Edges, nodes, focusSeverity, 10);
|
|
ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(solution.Edges, nodes, focusSeverity, 10);
|
|
|
|
if (focusSeverity.Count > 0)
|
|
{
|
|
var batchedRootEdgeIds = focusSeverity
|
|
.OrderByDescending(pair => pair.Value)
|
|
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
|
.Take(Math.Min(focusSeverity.Count, MaxWinnerPolishBatchedRootEdges + 2))
|
|
.Select(pair => pair.Key)
|
|
.ToArray();
|
|
var batchedFocusEdgeIds = ExpandWinningSolutionFocus(solution.Edges, batchedRootEdgeIds).ToArray();
|
|
if (batchedFocusEdgeIds.Length > 0)
|
|
{
|
|
var batchedCandidateEdges = ComposeTransactionalFinalDetourCandidate(
|
|
solution.Edges,
|
|
nodes,
|
|
minLineClearance,
|
|
batchedFocusEdgeIds);
|
|
batchedCandidateEdges = ChoosePreferredHardRuleLayout(solution.Edges, batchedCandidateEdges, nodes);
|
|
if (TryPromoteFinalDetourCandidate(
|
|
solution.Edges,
|
|
batchedCandidateEdges,
|
|
nodes,
|
|
solution.Score,
|
|
solution.RetryState,
|
|
out var batchedPromotedEdges))
|
|
{
|
|
var batchedPromotedScore = ElkEdgeRoutingScoring.ComputeScore(batchedPromotedEdges, nodes);
|
|
var batchedPromotedRetryState = BuildRetryState(
|
|
batchedPromotedScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(batchedPromotedEdges, nodes).Count
|
|
: 0);
|
|
|
|
solution = new CandidateSolution(
|
|
batchedPromotedScore,
|
|
batchedPromotedRetryState,
|
|
batchedPromotedEdges,
|
|
solution.StrategyIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
var candidateEdges = ApplyFinalDetourPolish(solution.Edges, nodes, minLineClearance, restrictedEdgeIds: null);
|
|
if (ReferenceEquals(candidateEdges, solution.Edges))
|
|
{
|
|
return solution;
|
|
}
|
|
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
|
|
: 0);
|
|
|
|
return new CandidateSolution(candidateScore, candidateRetryState, candidateEdges, solution.StrategyIndex);
|
|
}
|
|
|
|
internal static ElkRoutedEdge[] BuildFinalBoundarySlotCandidate(
|
|
ElkRoutedEdge[] edges,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance,
|
|
IReadOnlyCollection<string>? restrictedEdgeIds = null,
|
|
bool allowLateRestabilizedClosure = true)
|
|
{
|
|
var focusEdgeIds = restrictedEdgeIds?.Count > 0
|
|
? restrictedEdgeIds
|
|
: edges
|
|
.Select(edge => edge.Id)
|
|
.OrderBy(edgeId => edgeId, StringComparer.Ordinal)
|
|
.ToArray();
|
|
ElkLayoutDiagnostics.LogProgress(
|
|
$"Boundary-slot candidate start: focus={focusEdgeIds.Count} allowLateRestabilizedClosure={allowLateRestabilizedClosure}");
|
|
|
|
var best = edges;
|
|
var candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
edges,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after initial snap");
|
|
candidate = CloseRemainingTerminalViolations(candidate, nodes, direction, minLineClearance, restrictedEdgeIds);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after terminal closure");
|
|
if (focusEdgeIds.Count > 0)
|
|
{
|
|
candidate = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
candidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after face/shared-lane separation");
|
|
}
|
|
|
|
candidate = ClampBelowGraphEdges(candidate, nodes, restrictedEdgeIds);
|
|
candidate = ElkEdgePostProcessor.NormalizeBoundaryAngles(candidate, nodes);
|
|
candidate = ElkEdgePostProcessor.NormalizeSourceExitAngles(candidate, nodes);
|
|
candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after normalization snap");
|
|
candidate = ApplyPostSlotDetourClosure(candidate, nodes, minLineClearance, restrictedEdgeIds);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after detour closure");
|
|
candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after detour snap");
|
|
if (restrictedEdgeIds?.Count > 0)
|
|
{
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
CloseRemainingTerminalViolations(candidate, nodes, direction, minLineClearance, restrictedEdgeIds),
|
|
nodes);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after restricted terminal recheck");
|
|
}
|
|
|
|
candidate = ApplyLateBoundarySlotRestabilization(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after late restabilization");
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true),
|
|
nodes);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after late restabilization snap");
|
|
if (focusEdgeIds.Count > 0)
|
|
{
|
|
var lateHardRuleCandidate = ApplyAggressiveSharedLaneClosure(
|
|
candidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
lateHardRuleCandidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
lateHardRuleCandidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true);
|
|
candidate = ChoosePreferredHardRuleLayout(candidate, lateHardRuleCandidate, nodes);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true),
|
|
nodes);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after late hard-rule closure");
|
|
}
|
|
if (focusEdgeIds.Count > 0
|
|
&& (ElkEdgeRoutingScoring.CountBoundarySlotViolations(candidate, nodes) > 0
|
|
|| ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(candidate, nodes) > 0))
|
|
{
|
|
var lateClosureCandidate = ApplyLateFocusedBoundarySlotClosure(
|
|
candidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds,
|
|
restrictedEdgeIds);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(candidate, lateClosureCandidate, nodes);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true),
|
|
nodes);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after focused late closure");
|
|
}
|
|
|
|
if (allowLateRestabilizedClosure
|
|
&& focusEdgeIds.Count > 0
|
|
&& focusEdgeIds.Count <= MaxLateRestabilizedClosureFocusEdges)
|
|
{
|
|
var stagedLateRestabilizedCandidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
edges,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true);
|
|
stagedLateRestabilizedCandidate = BuildFinalRestabilizedCandidate(
|
|
stagedLateRestabilizedCandidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
restrictedEdgeIds);
|
|
stagedLateRestabilizedCandidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
stagedLateRestabilizedCandidate,
|
|
ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
stagedLateRestabilizedCandidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true),
|
|
nodes);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(candidate, stagedLateRestabilizedCandidate, nodes);
|
|
best = ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate after staged late restabilized closure");
|
|
}
|
|
|
|
ElkLayoutDiagnostics.LogProgress("Boundary-slot candidate complete");
|
|
return ChoosePreferredBoundarySlotRepairLayout(best, candidate, nodes);
|
|
}
|
|
|
|
internal static ElkRoutedEdge[] BuildFinalRestabilizedCandidate(
|
|
ElkRoutedEdge[] edges,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance,
|
|
IReadOnlyCollection<string>? restrictedEdgeIds = null)
|
|
{
|
|
var focusEdgeIds = restrictedEdgeIds?.Count > 0
|
|
? restrictedEdgeIds
|
|
: edges
|
|
.Select(edge => edge.Id)
|
|
.OrderBy(edgeId => edgeId, StringComparer.Ordinal)
|
|
.ToArray();
|
|
var focusEdgeSet = focusEdgeIds.ToHashSet(StringComparer.Ordinal);
|
|
|
|
var candidate = ChoosePreferredHardRuleLayout(
|
|
edges,
|
|
CloseRemainingTerminalViolations(edges, nodes, direction, minLineClearance, restrictedEdgeIds),
|
|
nodes);
|
|
if (focusEdgeIds.Count > 0)
|
|
{
|
|
candidate = ChoosePreferredHardRuleLayout(
|
|
candidate,
|
|
ApplyAggressiveSharedLaneClosure(
|
|
candidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds),
|
|
nodes);
|
|
}
|
|
|
|
candidate = BuildFinalBoundarySlotCandidate(
|
|
candidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
allowLateRestabilizedClosure: false);
|
|
if (focusEdgeIds.Count > 0)
|
|
{
|
|
candidate = ChoosePreferredHardRuleLayout(
|
|
candidate,
|
|
ApplyAggressiveSharedLaneClosure(
|
|
candidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds),
|
|
nodes);
|
|
}
|
|
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true),
|
|
nodes);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
CloseRemainingTerminalViolations(candidate, nodes, direction, minLineClearance, restrictedEdgeIds),
|
|
nodes);
|
|
if (focusEdgeIds.Count > 0)
|
|
{
|
|
var lateHardRuleCandidate = ApplyAggressiveSharedLaneClosure(
|
|
candidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
lateHardRuleCandidate = CloseRemainingTerminalViolations(
|
|
lateHardRuleCandidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
candidate = ChoosePreferredHardRuleLayout(candidate, lateHardRuleCandidate, nodes);
|
|
|
|
var remainingBadAngles = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
ElkEdgeRoutingScoring.CountBadBoundaryAngles(candidate, nodes, remainingBadAngles, 10);
|
|
var remainingTerminalFocus = ExpandWinningSolutionFocus(
|
|
candidate,
|
|
remainingBadAngles.Keys.Where(focusEdgeSet.Contains))
|
|
.Where(focusEdgeSet.Contains)
|
|
.OrderBy(edgeId => edgeId, StringComparer.Ordinal)
|
|
.ToArray();
|
|
if (remainingTerminalFocus.Length > 0)
|
|
{
|
|
var terminalFocus = (IReadOnlyCollection<string>)remainingTerminalFocus;
|
|
var lateTerminalCandidate = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
terminalFocus);
|
|
lateTerminalCandidate = BuildFinalBoundarySlotCandidate(
|
|
lateTerminalCandidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
terminalFocus,
|
|
allowLateRestabilizedClosure: false);
|
|
lateTerminalCandidate = CloseRemainingTerminalViolations(
|
|
lateTerminalCandidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
terminalFocus);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(candidate, lateTerminalCandidate, nodes);
|
|
}
|
|
}
|
|
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true),
|
|
nodes);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
ApplyPostSlotDetourClosure(candidate, nodes, minLineClearance, restrictedEdgeIds),
|
|
nodes);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true),
|
|
nodes);
|
|
candidate = ApplyLateBoundarySlotRestabilization(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true),
|
|
nodes);
|
|
|
|
if (focusEdgeIds.Count > 0)
|
|
{
|
|
var finalSharedLaneCandidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
candidate = ChoosePreferredSharedLanePolishLayout(candidate, finalSharedLaneCandidate, nodes);
|
|
|
|
var finalSourceJoinCandidate = ElkEdgePostProcessor.SpreadSourceDepartureJoins(
|
|
finalSharedLaneCandidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
candidate = ChoosePreferredSharedLanePolishLayout(candidate, finalSourceJoinCandidate, nodes);
|
|
|
|
var finalTargetJoinCandidate = ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
|
finalSourceJoinCandidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds,
|
|
forceOutwardAxisSpacing: true);
|
|
candidate = ChoosePreferredSharedLanePolishLayout(candidate, finalTargetJoinCandidate, nodes);
|
|
|
|
var finalAggressiveCandidate = ApplyAggressiveSharedLaneClosure(
|
|
candidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
candidate = ChoosePreferredSharedLanePolishLayout(candidate, finalAggressiveCandidate, nodes);
|
|
|
|
var forcedFinalSharedLaneCandidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
var baselineSharedLaneViolations = ElkEdgeRoutingScoring.CountSharedLaneViolations(candidate, nodes);
|
|
var forcedSharedLaneViolations = ElkEdgeRoutingScoring.CountSharedLaneViolations(forcedFinalSharedLaneCandidate, nodes);
|
|
if (forcedSharedLaneViolations < baselineSharedLaneViolations)
|
|
{
|
|
var baselineScore = ElkEdgeRoutingScoring.ComputeScore(candidate, nodes);
|
|
var forcedScore = ElkEdgeRoutingScoring.ComputeScore(forcedFinalSharedLaneCandidate, nodes);
|
|
if (forcedScore.NodeCrossings <= baselineScore.NodeCrossings)
|
|
{
|
|
candidate = forcedFinalSharedLaneCandidate;
|
|
}
|
|
}
|
|
}
|
|
|
|
return candidate;
|
|
}
|
|
|
|
private static ElkRoutedEdge[] ApplyLateBoundarySlotRestabilization(
|
|
ElkRoutedEdge[] edges,
|
|
ElkPositionedNode[] nodes,
|
|
double minLineClearance,
|
|
IReadOnlyCollection<string> focusEdgeIds)
|
|
{
|
|
if (focusEdgeIds.Count == 0)
|
|
{
|
|
return edges;
|
|
}
|
|
|
|
var candidate = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(
|
|
edges,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after mixed-face separation");
|
|
candidate = ElkEdgePostProcessor.SeparateRepeatCollectorLocalLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after collector separation");
|
|
candidate = ElkEdgePostProcessor.SpreadSourceDepartureJoins(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after source-join spread");
|
|
candidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after shared-lane separation");
|
|
candidate = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after boundary/target repair");
|
|
candidate = ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after target-join spread");
|
|
candidate = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after feeder-band spread");
|
|
candidate = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(candidate, nodes, focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after gateway finalize");
|
|
candidate = ElkEdgePostProcessor.NormalizeBoundaryAngles(candidate, nodes);
|
|
candidate = ElkEdgePostProcessor.NormalizeSourceExitAngles(candidate, nodes);
|
|
candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds,
|
|
enforceAllNodeEndpoints: true);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after first normalization snap");
|
|
candidate = ElkEdgePostProcessor.SeparateRepeatCollectorLocalLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after second collector separation");
|
|
candidate = ElkEdgePostProcessor.SpreadSourceDepartureJoins(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after second source-join spread");
|
|
candidate = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after second mixed-face separation");
|
|
candidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after second shared-lane separation");
|
|
candidate = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after second boundary/target repair");
|
|
candidate = ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after second target-join spread");
|
|
candidate = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after second feeder-band spread");
|
|
candidate = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(candidate, nodes, focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after second gateway finalize");
|
|
candidate = ApplyPostSlotDetourClosure(candidate, nodes, minLineClearance, focusEdgeIds);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization after detour closure");
|
|
candidate = ElkEdgePostProcessor.NormalizeBoundaryAngles(candidate, nodes);
|
|
candidate = ElkEdgePostProcessor.NormalizeSourceExitAngles(candidate, nodes);
|
|
candidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
focusEdgeIds,
|
|
enforceAllNodeEndpoints: true);
|
|
ElkLayoutDiagnostics.LogProgress("Late boundary-slot restabilization complete");
|
|
|
|
return ChoosePreferredBoundarySlotRepairLayout(edges, candidate, nodes);
|
|
}
|
|
|
|
private static ElkRoutedEdge[] ApplyLateFocusedBoundarySlotClosure(
|
|
ElkRoutedEdge[] edges,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance,
|
|
IReadOnlyCollection<string> focusEdgeIds,
|
|
IReadOnlyCollection<string>? restrictedEdgeIds)
|
|
{
|
|
var candidate = ChoosePreferredHardRuleLayout(
|
|
edges,
|
|
ApplyAggressiveSharedLaneClosure(edges, nodes, direction, minLineClearance, focusEdgeIds),
|
|
nodes);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true),
|
|
nodes);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
CloseRemainingTerminalViolations(candidate, nodes, direction, minLineClearance, restrictedEdgeIds),
|
|
nodes);
|
|
candidate = ApplyLateBoundarySlotRestabilization(candidate, nodes, minLineClearance, focusEdgeIds);
|
|
candidate = ChoosePreferredBoundarySlotRepairLayout(
|
|
candidate,
|
|
ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
|
candidate,
|
|
nodes,
|
|
minLineClearance,
|
|
restrictedEdgeIds,
|
|
enforceAllNodeEndpoints: true),
|
|
nodes);
|
|
return candidate;
|
|
}
|
|
|
|
private static bool TryPromoteFinalBoundarySlotCandidate(
|
|
CandidateSolution current,
|
|
ElkRoutedEdge[] candidateEdges,
|
|
ElkPositionedNode[] nodes,
|
|
out CandidateSolution promoted)
|
|
{
|
|
promoted = current;
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
|
|
: 0);
|
|
if (!IsBetterBoundarySlotRepairCandidate(
|
|
candidateScore,
|
|
candidateRetryState,
|
|
current.Score,
|
|
current.RetryState))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
promoted = current with
|
|
{
|
|
Score = candidateScore,
|
|
RetryState = candidateRetryState,
|
|
Edges = candidateEdges,
|
|
};
|
|
return true;
|
|
}
|
|
|
|
private static bool HasBlockingBoundarySlotPromotionRegression(
|
|
RoutingRetryState candidate,
|
|
RoutingRetryState baseline)
|
|
{
|
|
return candidate.RemainingShortHighways > baseline.RemainingShortHighways
|
|
|| candidate.RepeatCollectorCorridorViolations > baseline.RepeatCollectorCorridorViolations
|
|
|| candidate.RepeatCollectorNodeClearanceViolations > baseline.RepeatCollectorNodeClearanceViolations
|
|
|| candidate.BelowGraphViolations > baseline.BelowGraphViolations
|
|
|| candidate.UnderNodeViolations > baseline.UnderNodeViolations;
|
|
}
|
|
|
|
private static bool HasEdgeGeometryChanged(
|
|
IReadOnlyList<ElkRoutedEdge> baselineEdges,
|
|
IReadOnlyList<ElkRoutedEdge> candidateEdges,
|
|
string edgeId)
|
|
{
|
|
var baseline = baselineEdges.FirstOrDefault(edge => string.Equals(edge.Id, edgeId, StringComparison.Ordinal));
|
|
var candidate = candidateEdges.FirstOrDefault(edge => string.Equals(edge.Id, edgeId, StringComparison.Ordinal));
|
|
if (baseline is null || candidate is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var baselinePath = ExtractEdgePath(baseline);
|
|
var candidatePath = ExtractEdgePath(candidate);
|
|
if (baselinePath.Count != candidatePath.Count)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
for (var i = 0; i < baselinePath.Count; i++)
|
|
{
|
|
if (!ElkEdgeRoutingGeometry.PointsEqual(baselinePath[i], candidatePath[i]))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static List<ElkPoint> ExtractEdgePath(ElkRoutedEdge edge)
|
|
{
|
|
var path = new List<ElkPoint>();
|
|
foreach (var section in edge.Sections)
|
|
{
|
|
if (path.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(path[^1], section.StartPoint))
|
|
{
|
|
path.Add(section.StartPoint);
|
|
}
|
|
|
|
foreach (var bendPoint in section.BendPoints)
|
|
{
|
|
if (path.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(path[^1], bendPoint))
|
|
{
|
|
path.Add(bendPoint);
|
|
}
|
|
}
|
|
|
|
if (path.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(path[^1], section.EndPoint))
|
|
{
|
|
path.Add(section.EndPoint);
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
private static bool TryPromoteFinalHardRuleCandidate(
|
|
CandidateSolution current,
|
|
ElkRoutedEdge[] candidateEdges,
|
|
ElkPositionedNode[] nodes,
|
|
out CandidateSolution promoted)
|
|
{
|
|
promoted = current;
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
|
|
: 0);
|
|
if (!IsBetterBoundarySlotRepairCandidate(
|
|
candidateScore,
|
|
candidateRetryState,
|
|
current.Score,
|
|
current.RetryState))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
promoted = current with
|
|
{
|
|
Score = candidateScore,
|
|
RetryState = candidateRetryState,
|
|
Edges = candidateEdges,
|
|
};
|
|
return true;
|
|
}
|
|
|
|
private static ElkRoutedEdge[] ApplyAggressiveSharedLaneClosure(
|
|
ElkRoutedEdge[] edges,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance,
|
|
IReadOnlyCollection<string> focusEdgeIds)
|
|
{
|
|
var result = edges;
|
|
result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.SeparateRepeatCollectorLocalLaneConflicts(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkRepeatCollectorCorridors.SeparateSharedLanes(result, nodes, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, focusEdgeIds, forceOutwardAxisSpacing: true);
|
|
result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes, focusEdgeIds);
|
|
result = ClampBelowGraphEdges(result, nodes, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, direction, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes);
|
|
result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes);
|
|
result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, focusEdgeIds);
|
|
return result;
|
|
}
|
|
|
|
} |