Refactor ElkSharp hybrid routing and document speed path

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

View File

@@ -0,0 +1,522 @@
namespace StellaOps.ElkSharp;
internal static partial class ElkEdgeRouterIterative
{
private readonly record struct HybridRepairBatch(
string[] EdgeIds,
string[] ConflictKeys);
private static CandidateSolution OptimizeHybrid(
ElkRoutedEdge[] baselineProcessed,
EdgeRoutingScore baselineProcessedScore,
RoutingRetryState baselineRetryState,
ElkPositionedNode[] nodes,
ElkLayoutOptions layoutOptions,
IterativeRoutingConfig config,
double minLineClearance,
CancellationToken cancellationToken)
{
var strategy = BuildHybridStrategy(baselineProcessed, nodes, baselineProcessedScore, baselineRetryState, minLineClearance);
var current = new CandidateSolution(baselineProcessedScore, baselineRetryState, baselineProcessed, 0);
var diagnostics = ElkLayoutDiagnostics.Current;
var attemptCounter = 0;
ElkIterativeStrategyDiagnostics? liveStrategyDiagnostics = null;
if (diagnostics is not null)
{
liveStrategyDiagnostics = new ElkIterativeStrategyDiagnostics
{
StrategyIndex = 1,
OrderingName = "hybrid",
Attempts = 0,
TotalDurationMs = 0d,
BestScore = baselineProcessedScore,
Outcome = $"baseline({DescribeRetryState(baselineRetryState)})",
RegisteredLive = true,
BestEdges = baselineProcessed,
};
lock (diagnostics.SyncRoot)
{
diagnostics.IterativeStrategies.Add(liveStrategyDiagnostics);
}
ElkLayoutDiagnostics.FlushSnapshot(diagnostics);
}
ElkLayoutDiagnostics.LogProgress(
$"Hybrid routing start: score={current.Score.Value:F0} retry={DescribeRetryState(current.RetryState)} waves={config.MaxRepairWaves}");
for (var wave = 0; wave < config.MaxRepairWaves; wave++)
{
cancellationToken.ThrowIfCancellationRequested();
if (!current.RetryState.RequiresPrimaryRetry && current.Score.EdgeCrossings == 0)
{
ElkLayoutDiagnostics.LogProgress($"Hybrid routing converged before wave {wave + 1}");
break;
}
var repairPlan = BuildRepairPlan(
current.Edges,
nodes,
current.Score,
current.RetryState,
strategy,
wave + 1);
if (repairPlan is null || repairPlan.Value.EdgeIds.Length == 0)
{
ElkLayoutDiagnostics.LogProgress($"Hybrid routing wave {wave + 1}: no repair plan");
break;
}
var batches = BuildHybridRepairBatches(current.Edges, nodes, repairPlan.Value);
if (batches.Count == 0)
{
ElkLayoutDiagnostics.LogProgress($"Hybrid routing wave {wave + 1}: no independent repair batches");
break;
}
var waveImproved = false;
for (var batchIndex = 0; batchIndex < batches.Count; batchIndex++)
{
cancellationToken.ThrowIfCancellationRequested();
var batch = batches[batchIndex];
var batchPlan = BuildHybridBatchPlan(repairPlan.Value, batch.EdgeIds);
if (batchPlan.EdgeIds.Length == 0)
{
continue;
}
ElkLayoutDiagnostics.LogProgress(
$"Hybrid routing wave {wave + 1} batch {batchIndex + 1}/{batches.Count}: " +
$"edges=[{string.Join(", ", batchPlan.EdgeIds)}] keys=[{string.Join(", ", batch.ConflictKeys)}]");
var attemptStopwatch = System.Diagnostics.Stopwatch.StartNew();
if (!TryApplyHybridRepairBatch(
current,
nodes,
layoutOptions.Direction,
config,
strategy,
batchPlan,
cancellationToken,
out var promoted,
out var attempted,
out var routeDiagnostics))
{
attemptStopwatch.Stop();
attemptCounter++;
RecordHybridAttempt(
diagnostics,
liveStrategyDiagnostics,
attemptCounter,
attemptStopwatch.Elapsed.TotalMilliseconds,
attempted.Score,
BuildHybridAttemptOutcome(attempted.RetryState, improved: false),
routeDiagnostics,
attempted.Edges);
continue;
}
attemptStopwatch.Stop();
attemptCounter++;
RecordHybridAttempt(
diagnostics,
liveStrategyDiagnostics,
attemptCounter,
attemptStopwatch.Elapsed.TotalMilliseconds,
attempted.Score,
BuildHybridAttemptOutcome(attempted.RetryState, improved: true),
routeDiagnostics,
attempted.Edges);
current = promoted;
waveImproved = true;
}
if (waveImproved)
{
ElkLayoutDiagnostics.LogProgress(
$"Hybrid routing wave {wave + 1} improved: score={current.Score.Value:F0} retry={DescribeRetryState(current.RetryState)}");
continue;
}
if (!current.RetryState.RequiresPrimaryRetry && current.Score.EdgeCrossings == 0)
{
break;
}
ElkLayoutDiagnostics.LogProgress(
$"Hybrid routing wave {wave + 1} stalled; adapting strategy for retry={DescribeRetryState(current.RetryState)}");
strategy.AdaptForViolations(current.Score, wave, current.RetryState);
}
if (config.MaxRepairWaves <= 1
&& (current.RetryState.RequiresPrimaryRetry || current.Score.EdgeCrossings > 0))
{
var focusedRepairStopwatch = System.Diagnostics.Stopwatch.StartNew();
var focusedRepair = TryApplyVerifiedIssueRepairRound(
current.Edges,
nodes,
config.ObstacleMargin,
strategy,
current.RetryState,
layoutOptions.Direction,
cancellationToken,
config.MaxParallelRepairBuilds,
trustIndependentParallelBuilds: true,
useHybridCleanup: true);
focusedRepairStopwatch.Stop();
if (focusedRepair is { } repaired)
{
var improved = IsBetterBoundarySlotRepairCandidate(
repaired.Score,
repaired.RetryState,
current.Score,
current.RetryState);
attemptCounter++;
RecordHybridAttempt(
diagnostics,
liveStrategyDiagnostics,
attemptCounter,
focusedRepairStopwatch.Elapsed.TotalMilliseconds,
repaired.Score,
BuildHybridAttemptOutcome(repaired.RetryState, improved),
repaired.RouteDiagnostics,
repaired.Edges);
if (improved)
{
current = current with
{
Score = repaired.Score,
RetryState = repaired.RetryState,
Edges = repaired.Edges,
};
ElkLayoutDiagnostics.LogProgress(
$"Hybrid focused post-wave repair improved: score={current.Score.Value:F0} retry={DescribeRetryState(current.RetryState)}");
}
}
}
current = RefineHybridWinningSolution(
current,
nodes,
layoutOptions.Direction,
minLineClearance,
preferLowWaveRuntimePolish: config.MaxRepairWaves <= 2);
if (liveStrategyDiagnostics is not null)
{
lock (diagnostics!.SyncRoot)
{
liveStrategyDiagnostics.Attempts = attemptCounter;
liveStrategyDiagnostics.BestScore = current.Score;
liveStrategyDiagnostics.BestEdges = current.Edges;
liveStrategyDiagnostics.Outcome = current.RetryState.RequiresPrimaryRetry
? $"retry({DescribeRetryState(current.RetryState)})"
: "valid";
}
ElkLayoutDiagnostics.FlushSnapshot(diagnostics!);
}
ElkLayoutDiagnostics.LogProgress(
$"Hybrid routing complete: score={current.Score.Value:F0} retry={DescribeRetryState(current.RetryState)}");
return current;
}
private static RoutingStrategy BuildHybridStrategy(
ElkRoutedEdge[] edges,
ElkPositionedNode[] nodes,
EdgeRoutingScore baselineScore,
RoutingRetryState baselineRetryState,
double minLineClearance)
{
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
var connectionCount = new Dictionary<string, int>(StringComparer.Ordinal);
foreach (var edge in edges)
{
var sourceId = edge.SourceNodeId ?? string.Empty;
var targetId = edge.TargetNodeId ?? string.Empty;
connectionCount[sourceId] = connectionCount.GetValueOrDefault(sourceId) + 1;
connectionCount[targetId] = connectionCount.GetValueOrDefault(targetId) + 1;
}
var useConnectedOrdering = baselineRetryState.RequiresBlockingRetry
|| baselineRetryState.SharedLaneViolations > 0
|| baselineRetryState.TargetApproachJoinViolations > 0;
var edgeOrder = useConnectedOrdering
? OrderByMostConnectedFirst(edges, connectionCount)
: OrderByLongestFirst(edges, nodesById);
var routingParams = baselineRetryState.RequiresBlockingRetry
? new AStarRoutingParams(18d, 400d, 600d, 3.0d, minLineClearance, 40d, true)
: new AStarRoutingParams(18d, 200d, 500d, 2.0d, minLineClearance, 40d, true);
var strategy = new RoutingStrategy
{
EdgeOrder = edgeOrder,
BaseLineClearance = minLineClearance,
MinLineClearance = minLineClearance,
RoutingParams = routingParams,
};
if (baselineRetryState.RequiresPrimaryRetry || baselineScore.EdgeCrossings > 0)
{
strategy.AdaptForViolations(baselineScore, 0, baselineRetryState);
}
return strategy;
}
private static IReadOnlyList<HybridRepairBatch> BuildHybridRepairBatches(
IReadOnlyCollection<ElkRoutedEdge> edges,
IReadOnlyCollection<ElkPositionedNode> nodes,
RepairPlan repairPlan)
{
var edgesById = edges.ToDictionary(edge => edge.Id, StringComparer.Ordinal);
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
var orderedBatches = new List<(List<string> EdgeIds, HashSet<string> ConflictKeys)>();
foreach (var edgeId in repairPlan.EdgeIds)
{
if (!edgesById.TryGetValue(edgeId, out var edge))
{
continue;
}
var conflictKeys = GetHybridConflictKeys(edge, nodesById);
var assigned = false;
foreach (var batch in orderedBatches)
{
if (batch.ConflictKeys.Overlaps(conflictKeys))
{
continue;
}
batch.EdgeIds.Add(edgeId);
batch.ConflictKeys.UnionWith(conflictKeys);
assigned = true;
break;
}
if (assigned)
{
continue;
}
orderedBatches.Add((
EdgeIds: [edgeId],
ConflictKeys: new HashSet<string>(conflictKeys, StringComparer.Ordinal)));
}
return orderedBatches
.Select(batch => new HybridRepairBatch(
batch.EdgeIds.ToArray(),
batch.ConflictKeys.OrderBy(key => key, StringComparer.Ordinal).ToArray()))
.ToArray();
}
private static string[] GetHybridConflictKeys(
ElkRoutedEdge edge,
IReadOnlyDictionary<string, ElkPositionedNode> nodesById)
{
var keys = new HashSet<string>(StringComparer.Ordinal);
var sourceId = edge.SourceNodeId ?? string.Empty;
var targetId = edge.TargetNodeId ?? string.Empty;
if (!string.IsNullOrEmpty(sourceId))
{
keys.Add($"source:{sourceId}");
}
if (!string.IsNullOrEmpty(targetId))
{
keys.Add($"target:{targetId}");
}
if (!string.IsNullOrEmpty(sourceId) && !string.IsNullOrEmpty(targetId))
{
keys.Add($"pair:{sourceId}->{targetId}");
}
var path = ExtractPath(edge);
if (path.Count >= 2
&& nodesById.TryGetValue(sourceId, out var sourceNode))
{
var sourceSide = ElkEdgeRoutingGeometry.ResolveBoundaryApproachSide(path[0], path[1], sourceNode);
keys.Add($"source-side:{sourceId}:{sourceSide}");
if (ElkShapeBoundaries.IsGatewayShape(sourceNode))
{
keys.Add($"gateway-source:{sourceId}");
}
}
if (path.Count >= 2
&& nodesById.TryGetValue(targetId, out var targetNode))
{
var previousPoint = path[^2];
var targetSide = ResolveEntrySide(path[^1], previousPoint, targetNode);
keys.Add($"target-side:{targetId}:{targetSide}");
if (ElkShapeBoundaries.IsGatewayShape(targetNode))
{
keys.Add($"gateway-target:{targetId}");
}
}
if (ElkEdgePostProcessor.IsRepeatCollectorLabel(edge.Label))
{
keys.Add($"collector:{sourceId}:{targetId}");
}
return keys.OrderBy(key => key, StringComparer.Ordinal).ToArray();
}
private static RepairPlan BuildHybridBatchPlan(RepairPlan repairPlan, IReadOnlyCollection<string> batchEdgeIds)
{
var batchEdgeSet = batchEdgeIds.ToHashSet(StringComparer.Ordinal);
var edgeIds = repairPlan.EdgeIds.Where(batchEdgeSet.Contains).ToArray();
var preferredShortestEdgeIds = repairPlan.PreferredShortestEdgeIds.Where(batchEdgeSet.Contains).ToArray();
var routeRepairEdgeIds = repairPlan.RouteRepairEdgeIds.Where(batchEdgeSet.Contains).ToArray();
var edgeIndexById = new Dictionary<string, int>(StringComparer.Ordinal);
for (var i = 0; i < repairPlan.EdgeIds.Length && i < repairPlan.EdgeIndices.Length; i++)
{
edgeIndexById[repairPlan.EdgeIds[i]] = repairPlan.EdgeIndices[i];
}
var edgeIndices = edgeIds
.Select(edgeId => edgeIndexById.GetValueOrDefault(edgeId, -1))
.Where(index => index >= 0)
.ToArray();
return new RepairPlan(
edgeIndices,
edgeIds,
preferredShortestEdgeIds,
routeRepairEdgeIds,
repairPlan.Reasons);
}
private static bool TryApplyHybridRepairBatch(
CandidateSolution current,
ElkPositionedNode[] nodes,
ElkLayoutDirection direction,
IterativeRoutingConfig config,
RoutingStrategy strategy,
RepairPlan repairPlan,
CancellationToken cancellationToken,
out CandidateSolution promoted,
out CandidateSolution attempted,
out ElkIterativeRouteDiagnostics routeDiagnostics)
{
promoted = current;
attempted = current;
var restrictedEdgeIds = repairPlan.EdgeIds;
var routed = RepairPenalizedEdges(
current.Edges,
nodes,
config.ObstacleMargin,
strategy,
repairPlan,
cancellationToken,
config.MaxParallelRepairBuilds,
trustIndependentParallelBuilds: true);
routeDiagnostics = routed.Diagnostics;
var candidateEdges = ApplyHybridTerminalRuleCleanupRound(
routed.Edges,
nodes,
direction,
strategy.MinLineClearance,
restrictedEdgeIds);
candidateEdges = ChoosePreferredHardRuleLayout(current.Edges, candidateEdges, nodes);
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
var remainingBrokenHighways = HighwayProcessingEnabled
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
: 0;
var candidateRetryState = BuildRetryState(candidateScore, remainingBrokenHighways);
if (candidateRetryState.RequiresBlockingRetry || candidateRetryState.RequiresLengthRetry)
{
var focusedRepair = TryApplyVerifiedIssueRepairRound(
candidateEdges,
nodes,
config.ObstacleMargin,
strategy,
candidateRetryState,
direction,
cancellationToken,
config.MaxParallelRepairBuilds,
trustIndependentParallelBuilds: true,
useHybridCleanup: true);
if (focusedRepair is { } repaired
&& IsBetterBoundarySlotRepairCandidate(
repaired.Score,
repaired.RetryState,
candidateScore,
candidateRetryState))
{
candidateEdges = repaired.Edges;
candidateScore = repaired.Score;
remainingBrokenHighways = repaired.RemainingBrokenHighways;
candidateRetryState = repaired.RetryState;
}
}
attempted = current with
{
Score = candidateScore,
RetryState = candidateRetryState,
Edges = candidateEdges,
};
if (!IsBetterBoundarySlotRepairCandidate(
candidateScore,
candidateRetryState,
current.Score,
current.RetryState))
{
return false;
}
promoted = attempted;
return true;
}
private static void RecordHybridAttempt(
ElkLayoutRunDiagnostics? diagnostics,
ElkIterativeStrategyDiagnostics? liveStrategyDiagnostics,
int attempt,
double durationMs,
EdgeRoutingScore score,
string outcome,
ElkIterativeRouteDiagnostics routeDiagnostics,
ElkRoutedEdge[] edges)
{
if (diagnostics is null || liveStrategyDiagnostics is null)
{
return;
}
var attemptDiagnostics = new ElkIterativeAttemptDiagnostics
{
Attempt = attempt,
TotalDurationMs = Math.Round(durationMs, 3),
Score = score,
Outcome = outcome,
RouteDiagnostics = routeDiagnostics,
Edges = edges,
};
lock (diagnostics.SyncRoot)
{
liveStrategyDiagnostics.Attempts = attempt;
liveStrategyDiagnostics.BestScore = score;
liveStrategyDiagnostics.AttemptDetails.Add(attemptDiagnostics);
}
ElkLayoutDiagnostics.FlushSnapshot(diagnostics);
}
private static string BuildHybridAttemptOutcome(RoutingRetryState retryState, bool improved)
{
if (!retryState.RequiresPrimaryRetry)
{
return improved ? "valid" : "rejected-valid";
}
var retry = $"retry({DescribeRetryState(retryState)})";
return improved ? retry : $"rejected-{retry}";
}
}