Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.StrategyRepair.Evaluate.cs
master 8a28e25d05 Decompose EvaluateStrategy (644->480 lines) and close sprint 006 TASK-002/003/004
Extract BuildMaxRetryState, DetectStrategyStagnation, and DecideStrategyAttemptOutcome
into ElkEdgeRouterIterative.StrategyRepair.Evaluate.Helpers.cs (174 lines).

Sprint 006 status: TASK-002 DONE (hybrid parity coverage), TASK-003 DONE (file
decomposition), TASK-004 DONE (docs). TASK-001 remains DOING (conflict-zone scheduling).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 14:24:16 +03:00

481 lines
21 KiB
C#

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<ElkIterativeAttemptDiagnostics>();
var fallbackSolutions = new List<CandidateSolution>();
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<string>(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<ElkIterativePhaseDiagnostics>();
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] attempt {attempt + 1} start");
T MeasurePhase<T>(string phaseName, Func<T> 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);
}
}