285 lines
13 KiB
C#
285 lines
13 KiB
C#
namespace StellaOps.ElkSharp;
|
|
|
|
internal static partial class ElkEdgeRouterIterative
|
|
{
|
|
private static ElkRoutedEdge[] ApplyFinalDetourPolish(
|
|
ElkRoutedEdge[] edges,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
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 directFocus = (IReadOnlyCollection<string>)[edgeId];
|
|
var expandedFocus = ExpandWinningSolutionFocus(result, [edgeId])
|
|
.Where(id => restrictedSet is null || restrictedSet.Contains(id))
|
|
.OrderBy(id => id, StringComparer.Ordinal)
|
|
.ToArray();
|
|
if (expandedFocus.Length == 0)
|
|
{
|
|
expandedFocus = [edgeId];
|
|
}
|
|
|
|
var bestCandidateEdges = result;
|
|
var bestCandidateScore = currentScore;
|
|
var bestCandidateRetryState = currentRetryState;
|
|
|
|
void ConsiderDetourCandidate(ElkRoutedEdge[] candidate)
|
|
{
|
|
candidate = ChoosePreferredHardRuleLayout(result, candidate, nodes);
|
|
if (ReferenceEquals(candidate, result))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidate, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidate, nodes).Count
|
|
: 0);
|
|
|
|
var improvedDetours = candidateRetryState.ExcessiveDetourViolations < currentRetryState.ExcessiveDetourViolations;
|
|
if (HasHardRuleRegression(candidateRetryState, currentRetryState)
|
|
|| (!improvedDetours
|
|
&& !IsBetterBoundarySlotRepairCandidate(
|
|
candidateScore,
|
|
candidateRetryState,
|
|
currentScore,
|
|
currentRetryState)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ReferenceEquals(bestCandidateEdges, result)
|
|
|| IsBetterCandidate(candidateScore, candidateRetryState, bestCandidateScore, bestCandidateRetryState))
|
|
{
|
|
bestCandidateEdges = candidate;
|
|
bestCandidateScore = candidateScore;
|
|
bestCandidateRetryState = candidateRetryState;
|
|
}
|
|
}
|
|
|
|
ConsiderDetourCandidate(
|
|
ComposeDirectionalTransactionalFinalDetourCandidate(
|
|
result,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
directFocus));
|
|
|
|
if (expandedFocus.Length != 1
|
|
|| !string.Equals(expandedFocus[0], edgeId, StringComparison.Ordinal))
|
|
{
|
|
ConsiderDetourCandidate(
|
|
ComposeDirectionalTransactionalFinalDetourCandidate(
|
|
result,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
expandedFocus));
|
|
}
|
|
|
|
var focusedSeed = ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(result, nodes, directFocus);
|
|
if (!ReferenceEquals(focusedSeed, result))
|
|
{
|
|
ConsiderDetourCandidate(
|
|
BuildFinalRestabilizedCandidate(
|
|
focusedSeed,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
expandedFocus));
|
|
}
|
|
|
|
if (ReferenceEquals(bestCandidateEdges, result))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
result = bestCandidateEdges;
|
|
improved = true;
|
|
break;
|
|
}
|
|
|
|
if (!improved)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static bool TryPromoteFinalDetourCandidate(
|
|
ElkRoutedEdge[] baselineEdges,
|
|
ElkRoutedEdge[] candidateEdges,
|
|
ElkPositionedNode[] nodes,
|
|
EdgeRoutingScore baselineScore,
|
|
RoutingRetryState baselineRetryState,
|
|
out ElkRoutedEdge[] promotedEdges)
|
|
{
|
|
promotedEdges = baselineEdges;
|
|
if (ReferenceEquals(candidateEdges, baselineEdges))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
|
|
: 0);
|
|
|
|
var improvedDetours = candidateRetryState.ExcessiveDetourViolations < baselineRetryState.ExcessiveDetourViolations;
|
|
var improvedGatewaySource = candidateRetryState.GatewaySourceExitViolations < baselineRetryState.GatewaySourceExitViolations;
|
|
if (HasHardRuleRegression(candidateRetryState, baselineRetryState)
|
|
|| (!(improvedDetours || improvedGatewaySource)
|
|
&& !IsBetterBoundarySlotRepairCandidate(
|
|
candidateScore,
|
|
candidateRetryState,
|
|
baselineScore,
|
|
baselineRetryState)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
promotedEdges = candidateEdges;
|
|
return true;
|
|
}
|
|
|
|
private static ElkRoutedEdge[] ComposeTransactionalFinalDetourCandidate(
|
|
ElkRoutedEdge[] baseline,
|
|
ElkPositionedNode[] nodes,
|
|
double minLineClearance,
|
|
IReadOnlyCollection<string> focusedEdgeIds)
|
|
{
|
|
return ComposeDirectionalTransactionalFinalDetourCandidate(
|
|
baseline,
|
|
nodes,
|
|
ElkLayoutDirection.LeftToRight,
|
|
minLineClearance,
|
|
focusedEdgeIds);
|
|
}
|
|
|
|
private static ElkRoutedEdge[] ComposeDirectionalTransactionalFinalDetourCandidate(
|
|
ElkRoutedEdge[] baseline,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
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 BuildFinalRestabilizedCandidate(
|
|
candidate,
|
|
nodes,
|
|
direction,
|
|
minLineClearance,
|
|
focusedEdgeIds);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|