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(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); // Corridor grid spacing: use average node height so the A* grid cells // are node-sized. Edges route through wide corridors between node rows, // not through narrow gaps. The fine node-boundary lines (at obstacle // edge ± 18px margin) still provide precise resolution near nodes. var serviceNodesForGrid = nodes.Where(n => n.Kind is not "Start" and not "End").ToArray(); var avgNodeHeight = serviceNodesForGrid.Length > 0 ? serviceNodesForGrid.Average(n => n.Height) : 88d; var corridorGridSpacing = Math.Max(40d, avgNodeHeight); var routingParams = baselineRetryState.RequiresBlockingRetry ? new AStarRoutingParams(18d, 400d, 600d, 3.0d, minLineClearance, corridorGridSpacing, true) : new AStarRoutingParams(18d, 200d, 500d, 2.0d, minLineClearance, corridorGridSpacing, 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 BuildHybridRepairBatches( IReadOnlyCollection edges, IReadOnlyCollection nodes, RepairPlan repairPlan) { var edgesById = edges.ToDictionary(edge => edge.Id, StringComparer.Ordinal); var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal); // Build conflict zones: each repair edge gets a bounding box from its // routed path plus a margin. Two edges conflict if their zones overlap // spatially, or if they share a repeat-collector label on the same // source-target pair. var zones = new List<(string EdgeId, ConflictZone Zone)>(); foreach (var edgeId in repairPlan.EdgeIds) { if (!edgesById.TryGetValue(edgeId, out var edge)) { continue; } zones.Add((edgeId, BuildConflictZone(edge, nodesById))); } // Greedy first-fit batching: assign each edge to the first batch // whose existing zones don't spatially conflict. var orderedBatches = new List<(List EdgeIds, List Zones)>(); foreach (var (edgeId, zone) in zones) { var assigned = false; foreach (var batch in orderedBatches) { if (batch.Zones.Any(existing => existing.ConflictsWith(zone))) { continue; } batch.EdgeIds.Add(edgeId); batch.Zones.Add(zone); assigned = true; break; } if (!assigned) { orderedBatches.Add((EdgeIds: [edgeId], Zones: [zone])); } } return orderedBatches .Select(batch => new HybridRepairBatch( batch.EdgeIds.ToArray(), batch.Zones.SelectMany(z => z.DescriptiveKeys).Distinct(StringComparer.Ordinal) .OrderBy(key => key, StringComparer.Ordinal).ToArray())) .ToArray(); } /// /// Geometric conflict zone for an edge: bounding box of its routed path /// expanded by a margin, plus endpoint node IDs for collector-label /// conflict detection. /// private static ConflictZone BuildConflictZone( ElkRoutedEdge edge, IReadOnlyDictionary nodesById) { var path = ExtractPath(edge); const double margin = 40d; // Compute bounding box from path + source/target node extents. var minX = double.MaxValue; var minY = double.MaxValue; var maxX = double.MinValue; var maxY = double.MinValue; foreach (var point in path) { minX = Math.Min(minX, point.X); minY = Math.Min(minY, point.Y); maxX = Math.Max(maxX, point.X); maxY = Math.Max(maxY, point.Y); } // Include source/target node extents in the zone. if (nodesById.TryGetValue(edge.SourceNodeId ?? string.Empty, out var srcNode)) { minX = Math.Min(minX, srcNode.X); minY = Math.Min(minY, srcNode.Y); maxX = Math.Max(maxX, srcNode.X + srcNode.Width); maxY = Math.Max(maxY, srcNode.Y + srcNode.Height); } if (nodesById.TryGetValue(edge.TargetNodeId ?? string.Empty, out var tgtNode)) { minX = Math.Min(minX, tgtNode.X); minY = Math.Min(minY, tgtNode.Y); maxX = Math.Max(maxX, tgtNode.X + tgtNode.Width); maxY = Math.Max(maxY, tgtNode.Y + tgtNode.Height); } var isCollector = ElkEdgePostProcessor.IsRepeatCollectorLabel(edge.Label); var descriptiveKeys = new List(3); if (!string.IsNullOrEmpty(edge.SourceNodeId)) { descriptiveKeys.Add($"source:{edge.SourceNodeId}"); } if (!string.IsNullOrEmpty(edge.TargetNodeId)) { descriptiveKeys.Add($"target:{edge.TargetNodeId}"); } if (isCollector) { descriptiveKeys.Add($"collector:{edge.SourceNodeId}:{edge.TargetNodeId}"); } return new ConflictZone( MinX: minX - margin, MinY: minY - margin, MaxX: maxX + margin, MaxY: maxY + margin, SourceNodeId: edge.SourceNodeId ?? string.Empty, TargetNodeId: edge.TargetNodeId ?? string.Empty, IsCollector: isCollector, DescriptiveKeys: descriptiveKeys.ToArray()); } private readonly record struct ConflictZone( double MinX, double MinY, double MaxX, double MaxY, string SourceNodeId, string TargetNodeId, bool IsCollector, string[] DescriptiveKeys) { /// /// Two zones conflict if their bounding boxes overlap spatially, /// or if they are both collectors on the same source-target pair. /// internal bool ConflictsWith(ConflictZone other) { // Collector edges on the same pair always conflict. if (IsCollector && other.IsCollector && string.Equals(SourceNodeId, other.SourceNodeId, StringComparison.Ordinal) && string.Equals(TargetNodeId, other.TargetNodeId, StringComparison.Ordinal)) { return true; } // Spatial overlap check. return MinX < other.MaxX && MaxX > other.MinX && MinY < other.MaxY && MaxY > other.MinY; } } private static RepairPlan BuildHybridBatchPlan(RepairPlan repairPlan, IReadOnlyCollection 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(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}"; } }