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>
175 lines
6.9 KiB
C#
175 lines
6.9 KiB
C#
using System.Diagnostics;
|
|
|
|
namespace StellaOps.ElkSharp;
|
|
|
|
internal static partial class ElkEdgeRouterIterative
|
|
{
|
|
/// <summary>
|
|
/// Creates a worst-case retry state used as the initial "best" baseline
|
|
/// in the strategy evaluation loop.
|
|
/// </summary>
|
|
private static RoutingRetryState BuildMaxRetryState()
|
|
{
|
|
return new RoutingRetryState(
|
|
RemainingShortHighways: int.MaxValue,
|
|
RepeatCollectorCorridorViolations: int.MaxValue,
|
|
RepeatCollectorNodeClearanceViolations: int.MaxValue,
|
|
TargetApproachJoinViolations: int.MaxValue,
|
|
TargetApproachBacktrackingViolations: int.MaxValue,
|
|
ExcessiveDetourViolations: int.MaxValue,
|
|
SharedLaneViolations: int.MaxValue,
|
|
BoundarySlotViolations: int.MaxValue,
|
|
BelowGraphViolations: int.MaxValue,
|
|
UnderNodeViolations: int.MaxValue,
|
|
LongDiagonalViolations: int.MaxValue,
|
|
ProximityViolations: int.MaxValue,
|
|
EntryAngleViolations: int.MaxValue,
|
|
GatewaySourceExitViolations: int.MaxValue,
|
|
LabelProximityViolations: int.MaxValue,
|
|
EdgeCrossings: int.MaxValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks stagnation conditions: repeated repair focus, repeated plateau
|
|
/// fingerprints, and blocking cycle detection. Returns the stagnation
|
|
/// outcome reason or null if the attempt should continue.
|
|
/// </summary>
|
|
private static string? DetectStrategyStagnation(
|
|
RepairPlan? repairPlan,
|
|
RoutingRetryState retryState,
|
|
RoutingRetryState bestAttemptRetryState,
|
|
int attempt,
|
|
ref string? lastRepairFocusFingerprint,
|
|
ref int repeatedRepairFocusCount,
|
|
ref string? lastPlateauFingerprint,
|
|
ref int repeatedPlateauFingerprintCount,
|
|
List<string> recentBlockingCycleFingerprints)
|
|
{
|
|
var repairFocusFingerprint = BuildRepairFocusFingerprint(repairPlan);
|
|
if (!string.IsNullOrEmpty(repairFocusFingerprint))
|
|
{
|
|
if (string.Equals(repairFocusFingerprint, lastRepairFocusFingerprint, StringComparison.Ordinal))
|
|
{
|
|
repeatedRepairFocusCount++;
|
|
}
|
|
else
|
|
{
|
|
lastRepairFocusFingerprint = repairFocusFingerprint;
|
|
repeatedRepairFocusCount = 0;
|
|
}
|
|
|
|
if (repeatedRepairFocusCount >= 1 && attempt >= 3)
|
|
{
|
|
return $"stalled-same-focus({DescribeRetryState(retryState)})@attempt{attempt + 1}";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lastRepairFocusFingerprint = null;
|
|
repeatedRepairFocusCount = 0;
|
|
}
|
|
|
|
var plateauFingerprint = BuildPlateauFingerprint(retryState, repairPlan);
|
|
if (string.Equals(plateauFingerprint, lastPlateauFingerprint, StringComparison.Ordinal))
|
|
{
|
|
repeatedPlateauFingerprintCount++;
|
|
}
|
|
else
|
|
{
|
|
lastPlateauFingerprint = plateauFingerprint;
|
|
repeatedPlateauFingerprintCount = 0;
|
|
}
|
|
|
|
if (repeatedPlateauFingerprintCount >= 2 && attempt >= 3)
|
|
{
|
|
return $"stalled-repeat({DescribeRetryState(retryState)})@attempt{attempt + 1}";
|
|
}
|
|
|
|
var blockingCycleFingerprint = BuildBlockingCycleFingerprint(retryState, repairPlan);
|
|
if (ShouldStopForBlockingCycle(
|
|
recentBlockingCycleFingerprints,
|
|
blockingCycleFingerprint,
|
|
retryState,
|
|
bestAttemptRetryState,
|
|
attempt))
|
|
{
|
|
return $"stalled-cycle({DescribeRetryState(retryState)})@attempt{attempt + 1}";
|
|
}
|
|
|
|
AppendRecentFingerprint(recentBlockingCycleFingerprints, blockingCycleFingerprint, 4);
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines the retry/accept decision for a strategy attempt based on
|
|
/// violation categories. Returns the attempt outcome string and whether
|
|
/// the loop should continue, break, or accept.
|
|
/// </summary>
|
|
private static (string Outcome, bool ShouldContinue, bool IsValid) DecideStrategyAttemptOutcome(
|
|
EdgeRoutingScore score,
|
|
RoutingRetryState retryState,
|
|
int maxAllowedNodeCrossings,
|
|
int attempt,
|
|
int maxAttempts,
|
|
StrategyWorkItem workItem,
|
|
RoutingStrategy strategy)
|
|
{
|
|
if (score.NodeCrossings > maxAllowedNodeCrossings)
|
|
{
|
|
strategy.AdaptForViolations(score, attempt, retryState);
|
|
return ($"hard-violation(nc={score.NodeCrossings}>{maxAllowedNodeCrossings})@attempt{attempt + 1}", true, false);
|
|
}
|
|
|
|
if (retryState.RemainingShortHighways > 0
|
|
|| retryState.RepeatCollectorCorridorViolations > 0
|
|
|| retryState.RepeatCollectorNodeClearanceViolations > 0
|
|
|| retryState.TargetApproachJoinViolations > 0
|
|
|| retryState.TargetApproachBacktrackingViolations > 0
|
|
|| retryState.SharedLaneViolations > 0
|
|
|| retryState.BelowGraphViolations > 0
|
|
|| retryState.UnderNodeViolations > 0
|
|
|| retryState.LongDiagonalViolations > 0
|
|
|| retryState.EntryAngleViolations > 0
|
|
|| retryState.GatewaySourceExitViolations > 0)
|
|
{
|
|
if (ShouldRetryForPrimaryViolations(retryState, attempt, maxAttempts))
|
|
{
|
|
strategy.AdaptForViolations(score, attempt, retryState);
|
|
return ($"retry({DescribeRetryState(retryState)})@attempt{attempt + 1}", true, false);
|
|
}
|
|
|
|
return ($"invalid({DescribeRetryState(retryState)})@attempt{attempt + 1}", false, false);
|
|
}
|
|
|
|
if (retryState.RequiresLengthRetry)
|
|
{
|
|
if (ShouldRetryForPrimaryViolations(retryState, attempt, maxAttempts))
|
|
{
|
|
strategy.AdaptForViolations(score, attempt, retryState);
|
|
return ($"retry({DescribeRetryState(retryState)})@attempt{attempt + 1}", true, false);
|
|
}
|
|
|
|
return ($"invalid({DescribeRetryState(retryState)})@attempt{attempt + 1}", false, false);
|
|
}
|
|
|
|
if (retryState.RequiresQualityRetry
|
|
&& ShouldRetryForPrimaryViolations(retryState, attempt, maxAttempts))
|
|
{
|
|
strategy.AdaptForViolations(score, attempt, retryState);
|
|
return ($"retry({DescribeRetryState(retryState)})@attempt{attempt + 1}", true, false);
|
|
}
|
|
|
|
if (ShouldRetryForEdgeCrossings(retryState, attempt, maxAttempts))
|
|
{
|
|
strategy.AdaptForViolations(score, attempt, retryState);
|
|
return ($"retry(edge-crossings={retryState.EdgeCrossings})@attempt{attempt + 1}", true, false);
|
|
}
|
|
|
|
var residualSoftViolations = retryState.RequiresQualityRetry || retryState.EdgeCrossings > 0;
|
|
var validOutcome = residualSoftViolations
|
|
? $"valid-soft({DescribeRetryState(retryState)})@attempt{attempt + 1}"
|
|
: $"valid@attempt{attempt + 1}";
|
|
return (validOutcome, false, true);
|
|
}
|
|
}
|