using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { private static StrategyEvaluationResult EvaluateStrategy( StrategyWorkItem workItem, ElkRoutedEdge[] baselineEdges, ElkPositionedNode[] nodes, ElkLayoutOptions layoutOptions, IterativeRoutingConfig config, CancellationToken cancellationToken, ElkLayoutRunDiagnostics? diagnostics) { using var diagnosticsScope = diagnostics is null ? null : ElkLayoutDiagnostics.Attach(diagnostics); const int maxAllowedNodeCrossings = 0; var strategy = workItem.Strategy; ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] start: bend={strategy.RoutingParams.BendPenalty:F0} " + $"diag={strategy.RoutingParams.DiagonalPenalty:F0} soft={strategy.RoutingParams.SoftObstacleWeight:F2} " + $"clearance={strategy.MinLineClearance:F1}"); var bestAttemptScore = (EdgeRoutingScore?)null; ElkRoutedEdge[]? bestAttemptEdges = null; var bestAttemptRetryState = BuildMaxRetryState(); var repairSeedScore = (EdgeRoutingScore?)null; ElkRoutedEdge[]? repairSeedEdges = null; var repairSeedRetryState = BuildMaxRetryState(); var attemptDetails = new List(); var fallbackSolutions = new List(); CandidateSolution? validSolution = null; var outcome = "no-valid"; var attempts = 0; var maxAttempts = config.MaxAdaptationsPerStrategy; var stagnantAttempts = 0; string? lastPlateauFingerprint = null; var repeatedPlateauFingerprintCount = 0; var recentBlockingCycleFingerprints = new List(4); string? lastPlannedRepairFocusFingerprint = null; var repeatedPlannedRepairFocusCount = 0; string? lastRepairFocusFingerprint = null; var repeatedRepairFocusCount = 0; var hasLastAttemptState = false; var lastAttemptRetryState = BuildMaxRetryState(); var lastAttemptNodeCrossings = int.MaxValue; var consecutiveNonImprovingAttempts = 0; var strategyStopwatch = Stopwatch.StartNew(); ElkIterativeStrategyDiagnostics? liveStrategyDiagnostics = null; if (diagnostics is not null) { liveStrategyDiagnostics = new ElkIterativeStrategyDiagnostics { StrategyIndex = workItem.StrategyIndex, OrderingName = workItem.StrategyName, Attempts = 0, TotalDurationMs = 0, BestScore = null, Outcome = "running", BendPenalty = workItem.Strategy.RoutingParams.BendPenalty, DiagonalPenalty = workItem.Strategy.RoutingParams.DiagonalPenalty, SoftObstacleWeight = workItem.Strategy.RoutingParams.SoftObstacleWeight, RegisteredLive = true, }; lock (diagnostics.SyncRoot) { diagnostics.IterativeStrategies.Add(liveStrategyDiagnostics); } ElkLayoutDiagnostics.FlushSnapshot(diagnostics); } for (var attempt = 0; attempt < maxAttempts; attempt++) { cancellationToken.ThrowIfCancellationRequested(); attempts++; var attemptStopwatch = Stopwatch.StartNew(); var phaseTimings = new List(); ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] attempt {attempt + 1} start"); T MeasurePhase(string phaseName, Func action) { var phaseStopwatch = Stopwatch.StartNew(); var value = action(); phaseStopwatch.Stop(); phaseTimings.Add(new ElkIterativePhaseDiagnostics { Phase = phaseName, DurationMs = Math.Round(phaseStopwatch.Elapsed.TotalMilliseconds, 3), }); return value; } // Phase 1: Route or repair edges. RepairPlan? repairPlan = null; RouteAllEdgesResult? routeResult; if (attempt == 0 || repairSeedEdges is null || repairSeedScore is null) { routeResult = MeasurePhase( "route-all-edges", () => RouteAllEdges(baselineEdges, nodes, config.ObstacleMargin, strategy, cancellationToken)); } else { repairPlan = MeasurePhase( "select-repair-targets", () => BuildRepairPlan(repairSeedEdges, nodes, repairSeedScore.Value, repairSeedRetryState, strategy, attempt)); if (repairPlan is null) { outcome = $"no-repair-targets({DescribeRetryState(repairSeedRetryState)})@attempt{attempt + 1}"; ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}"); break; } var plannedRepairFocusFingerprint = BuildRepairFocusFingerprint(repairPlan); if (!string.IsNullOrEmpty(plannedRepairFocusFingerprint)) { if (string.Equals(plannedRepairFocusFingerprint, lastPlannedRepairFocusFingerprint, StringComparison.Ordinal)) { repeatedPlannedRepairFocusCount++; } else { lastPlannedRepairFocusFingerprint = plannedRepairFocusFingerprint; repeatedPlannedRepairFocusCount = 0; } if (repeatedPlannedRepairFocusCount >= 1 && attempt >= 2) { outcome = $"stalled-same-repair-plan({DescribeRetryState(repairSeedRetryState)})@attempt{attempt + 1}"; ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}"); break; } } else { lastPlannedRepairFocusFingerprint = null; repeatedPlannedRepairFocusCount = 0; } ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] attempt {attempt + 1} local-repair " + $"edges=[{string.Join(", ", repairPlan.Value.EdgeIds)}] reasons=[{string.Join(", ", repairPlan.Value.Reasons)}]"); routeResult = MeasurePhase( "route-penalized-edges", () => RepairPenalizedEdges( repairSeedEdges, nodes, config.ObstacleMargin, strategy, repairPlan.Value, cancellationToken, config.MaxParallelRepairBuilds)); } if (routeResult is null) { outcome = "route-failed"; ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}@attempt{attempt + 1}"); break; } // Phase 2: Post-process routed edges. var candidateEdges = routeResult.Edges; var scopedCleanupEdgeIds = routeResult.Diagnostics?.Mode == "local-repair" ? routeResult.Diagnostics.RepairedEdgeIds.ToArray() : null; if (scopedCleanupEdgeIds is { Length: > 0 }) { candidateEdges = MeasurePhase( "targeted-terminal-rule-cleanup", () => ApplyTerminalRuleCleanupRound( candidateEdges, nodes, layoutOptions.Direction, strategy.MinLineClearance, scopedCleanupEdgeIds)); } else { candidateEdges = MeasurePhase( "snap-anchors", () => ElkEdgePostProcessor.SnapAnchorsToNodeBoundary(candidateEdges, nodes)); candidateEdges = MeasurePhase( "eliminate-diagonals", () => ElkEdgePostProcessor.EliminateDiagonalSegments(candidateEdges, nodes)); candidateEdges = MeasurePhase( "avoid-node-crossings-1", () => ElkEdgePostProcessor.AvoidNodeCrossings(candidateEdges, nodes, layoutOptions.Direction)); candidateEdges = MeasurePhase( "simplify-1", () => ElkEdgePostProcessorSimplify.SimplifyEdgePaths(candidateEdges, nodes)); candidateEdges = MeasurePhase( "tighten-corridors", () => ElkEdgePostProcessorSimplify.TightenOuterCorridors(candidateEdges, nodes)); if (HighwayProcessingEnabled) { candidateEdges = MeasurePhase( "break-short-highways", () => ElkEdgeRouterHighway.BreakShortHighways(candidateEdges, nodes)); } candidateEdges = MeasurePhase( "terminal-rule-cleanup", () => ApplyTerminalRuleCleanupRound( candidateEdges, nodes, layoutOptions.Direction, strategy.MinLineClearance)); } // Phase 3: Score and verify. var score = MeasurePhase( "compute-score", () => ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes)); var remainingBrokenHighways = HighwayProcessingEnabled ? MeasurePhase( "detect-broken-highways", () => ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count) : 0; var retryState = BuildRetryState(score, remainingBrokenHighways); if (retryState.RequiresBlockingRetry || retryState.RequiresLengthRetry) { var focusedRepair = MeasurePhase( "repair-verified-issues", () => TryApplyVerifiedIssueRepairRound( candidateEdges, nodes, config.ObstacleMargin, strategy, retryState, layoutOptions.Direction, cancellationToken, config.MaxParallelRepairBuilds)); if (focusedRepair is { } repaired && IsBetterBoundarySlotRepairCandidate( repaired.Score, repaired.RetryState, score, retryState)) { candidateEdges = repaired.Edges; score = repaired.Score; remainingBrokenHighways = repaired.RemainingBrokenHighways; retryState = repaired.RetryState; } } if (retryState.RequiresBlockingRetry || retryState.RequiresLengthRetry) { var stabilizedEdges = MeasurePhase( "stabilize-terminal-rules", () => ApplyTerminalRuleCleanupRound( candidateEdges, nodes, layoutOptions.Direction, strategy.MinLineClearance, scopedCleanupEdgeIds)); var stabilizedScore = MeasurePhase( "compute-score-stabilized", () => ElkEdgeRoutingScoring.ComputeScore(stabilizedEdges, nodes)); var stabilizedBrokenHighways = HighwayProcessingEnabled ? MeasurePhase( "detect-broken-highways-stabilized", () => ElkEdgeRouterHighway.DetectRemainingBrokenHighways(stabilizedEdges, nodes).Count) : 0; var stabilizedRetryState = BuildRetryState(stabilizedScore, stabilizedBrokenHighways); if (IsBetterBoundarySlotRepairCandidate( stabilizedScore, stabilizedRetryState, score, retryState)) { candidateEdges = stabilizedEdges; score = stabilizedScore; remainingBrokenHighways = stabilizedBrokenHighways; retryState = stabilizedRetryState; } } if (attempt == 0) { maxAttempts = DetermineAdaptiveAttemptBudget(retryState, config.MaxAdaptationsPerStrategy); ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] attempt-budget={maxAttempts}"); } ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] attempt {attempt + 1} " + $"score={score.Value:F0} retry={DescribeRetryState(retryState)}"); var candidate = new CandidateSolution(score, retryState, candidateEdges, workItem.StrategyIndex); fallbackSolutions.Add(candidate); repairSeedScore = score; repairSeedEdges = candidateEdges; repairSeedRetryState = retryState; // Phase 4: Stagnation detection. var stagnationReason = DetectStrategyStagnation( repairPlan, retryState, bestAttemptRetryState, attempt, ref lastRepairFocusFingerprint, ref repeatedRepairFocusCount, ref lastPlateauFingerprint, ref repeatedPlateauFingerprintCount, recentBlockingCycleFingerprints); if (stagnationReason is not null) { outcome = stagnationReason; ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}"); break; } // Phase 5: Track improvement. if (hasLastAttemptState) { var retryStateComparison = CompareRetryStates(retryState, lastAttemptRetryState); var improvedBoundarySlotsSinceLast = retryState.BoundarySlotViolations < lastAttemptRetryState.BoundarySlotViolations && score.NodeCrossings <= lastAttemptNodeCrossings && !HasBlockingBoundarySlotPromotionRegression(retryState, lastAttemptRetryState); if (!improvedBoundarySlotsSinceLast && retryStateComparison >= 0 && score.NodeCrossings >= lastAttemptNodeCrossings) { consecutiveNonImprovingAttempts++; } else { consecutiveNonImprovingAttempts = 0; } if (consecutiveNonImprovingAttempts >= 2 && attempt >= 3) { outcome = $"stalled-no-progress({DescribeRetryState(retryState)})@attempt{attempt + 1}"; ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}"); break; } } hasLastAttemptState = true; lastAttemptRetryState = retryState; lastAttemptNodeCrossings = score.NodeCrossings; var improvedAttempt = bestAttemptScore is null || IsBetterBoundarySlotRepairCandidate(score, retryState, bestAttemptScore.Value, bestAttemptRetryState); var improvedRuleState = bestAttemptScore is null || (retryState.BoundarySlotViolations < bestAttemptRetryState.BoundarySlotViolations && score.NodeCrossings <= bestAttemptScore.Value.NodeCrossings && !HasBlockingBoundarySlotPromotionRegression(retryState, bestAttemptRetryState)) || CompareRetryStates(retryState, bestAttemptRetryState) < 0 || score.NodeCrossings < bestAttemptScore.Value.NodeCrossings; if (improvedAttempt) { bestAttemptScore = score; bestAttemptEdges = candidateEdges; bestAttemptRetryState = retryState; stagnantAttempts = improvedRuleState ? 0 : stagnantAttempts + 1; } else { stagnantAttempts++; } if (ShouldStopForStagnation(stagnantAttempts, attempt, config.MaxAdaptationsPerStrategy)) { outcome = $"stalled({DescribeRetryState(retryState)})@attempt{attempt + 1}"; ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}"); break; } // Phase 6: Retry decision. var (attemptOutcome, shouldContinue, isValid) = DecideStrategyAttemptOutcome( score, retryState, maxAllowedNodeCrossings, attempt, maxAttempts, workItem, strategy); if (diagnostics is not null) { attemptStopwatch.Stop(); var attemptDiagnostics = new ElkIterativeAttemptDiagnostics { Attempt = attempt + 1, TotalDurationMs = Math.Round(attemptStopwatch.Elapsed.TotalMilliseconds, 3), Score = score, Outcome = attemptOutcome, RouteDiagnostics = routeResult.Diagnostics, Edges = candidateEdges, }; attemptDiagnostics.PhaseTimings.AddRange(phaseTimings); attemptDetails.Add(attemptDiagnostics); if (liveStrategyDiagnostics is not null) { lock (diagnostics.SyncRoot) { liveStrategyDiagnostics.Attempts = attempts; liveStrategyDiagnostics.TotalDurationMs = Math.Round(strategyStopwatch.Elapsed.TotalMilliseconds, 3); liveStrategyDiagnostics.BestScore = bestAttemptScore; liveStrategyDiagnostics.Outcome = attemptOutcome; liveStrategyDiagnostics.AttemptDetails.Add(attemptDiagnostics); } ElkLayoutDiagnostics.FlushSnapshot(diagnostics); } } if (shouldContinue) { outcome = attemptOutcome; ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting"); continue; } if (isValid) { outcome = attemptOutcome; ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}"); validSolution = candidate; break; } outcome = attemptOutcome; ElkLayoutDiagnostics.LogProgress( $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}"); break; } var stratDiag = new ElkIterativeStrategyDiagnostics { StrategyIndex = workItem.StrategyIndex, OrderingName = workItem.StrategyName, Attempts = attempts, TotalDurationMs = Math.Round(strategyStopwatch.Elapsed.TotalMilliseconds, 3), BestScore = bestAttemptScore, Outcome = outcome, BendPenalty = workItem.Strategy.RoutingParams.BendPenalty, DiagonalPenalty = workItem.Strategy.RoutingParams.DiagonalPenalty, SoftObstacleWeight = workItem.Strategy.RoutingParams.SoftObstacleWeight, BestEdges = bestAttemptEdges, }; stratDiag.AttemptDetails.AddRange(attemptDetails); if (liveStrategyDiagnostics is not null && diagnostics is not null) { lock (diagnostics.SyncRoot) { liveStrategyDiagnostics.Attempts = attempts; liveStrategyDiagnostics.TotalDurationMs = Math.Round(strategyStopwatch.Elapsed.TotalMilliseconds, 3); liveStrategyDiagnostics.BestScore = bestAttemptScore; liveStrategyDiagnostics.Outcome = outcome; liveStrategyDiagnostics.BestEdges = bestAttemptEdges; } ElkLayoutDiagnostics.FlushSnapshot(diagnostics); } return new StrategyEvaluationResult( workItem.StrategyIndex, fallbackSolutions, validSolution, stratDiag); } }