Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs
master 7e62f9c0c4 Add weighted under-node elevation in winner refinement
Runs ElevateUnderNodeViolations as a final pass using weighted score
comparison (Score.Value) instead of per-category gating. Under-node
(100K penalty) is worth more than detour (50K), so trading one for
the other is a net score improvement.

Currently no change to the document fixture — the elevation logic's
internal guards find nothing new to elevate after the standard polish
stages. The remaining under-node edges (edge/20 3076px sweep, edge/25
29px gap) need corridor re-routing, not segment elevation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 08:59:22 +03:00

138 lines
6.9 KiB
C#

namespace StellaOps.ElkSharp;
internal static partial class ElkEdgeRouterIterative
{
private static CandidateSolution RefineHybridWinningSolution(
CandidateSolution best,
ElkPositionedNode[] nodes,
ElkLayoutDirection direction,
double minLineClearance,
bool preferLowWaveRuntimePolish = false)
{
static string DescribeSolution(CandidateSolution solution)
{
return $"score={solution.Score.Value:F0} retry={DescribeRetryState(solution.RetryState)}";
}
var current = best;
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement start: {DescribeSolution(current)}");
if (current.RetryState.UnderNodeViolations > 0)
{
current = ApplyFinalDirectUnderNodePolish(current, nodes, minLineClearance);
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after under-node polish: {DescribeSolution(current)}");
}
if (current.RetryState.UnderNodeViolations > 0
|| current.RetryState.TargetApproachJoinViolations > 0)
{
current = ApplyFinalProtectedLocalBundlePolish(current, nodes, minLineClearance);
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after local-bundle polish: {DescribeSolution(current)}");
}
if (current.RetryState.SharedLaneViolations > 0
|| current.RetryState.TargetApproachJoinViolations > 0)
{
current = ApplyFinalSharedLanePolish(
current,
nodes,
direction,
minLineClearance,
preferLeanTerminalCleanup: preferLowWaveRuntimePolish);
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after shared-lane polish: {DescribeSolution(current)}");
}
if (current.RetryState.BoundarySlotViolations > 0
|| current.RetryState.GatewaySourceExitViolations > 0
|| current.RetryState.EntryAngleViolations > 0)
{
current = ApplyFinalBoundarySlotPolish(current, nodes, direction, minLineClearance, maxRounds: 1);
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after boundary-slot polish: {DescribeSolution(current)}");
}
if (current.RetryState.ExcessiveDetourViolations > 0
|| (!preferLowWaveRuntimePolish && current.RetryState.GatewaySourceExitViolations > 0))
{
current = ApplyWinnerDetourPolish(current, nodes, minLineClearance);
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after detour polish: {DescribeSolution(current)}");
}
if (HasHybridHardRulePressure(current.RetryState))
{
current = preferLowWaveRuntimePolish
? ApplyHybridLeanPostSlotHardRulePolish(current, nodes, direction, minLineClearance)
: ApplyFinalPostSlotHardRulePolish(current, nodes, direction, minLineClearance, maxRounds: 1);
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after post-slot hard-rule polish: {DescribeSolution(current)}");
}
// Final gateway backtracking repair: run NormalizeBoundaryAngles one
// last time to catch gateway target overshoots that earlier pipeline
// steps may have re-introduced. Accept with net-total comparison.
if (current.RetryState.TargetApproachBacktrackingViolations > 0
|| current.RetryState.EntryAngleViolations > 0)
{
var finalNormalized = ElkEdgePostProcessor.NormalizeBoundaryAngles(current.Edges, nodes);
finalNormalized = ElkEdgePostProcessor.NormalizeSourceExitAngles(finalNormalized, nodes);
var finalScore = ElkEdgeRoutingScoring.ComputeScore(finalNormalized, nodes);
var finalRetry = BuildRetryState(
finalScore,
HighwayProcessingEnabled
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(finalNormalized, nodes).Count
: 0);
var currentHard = CountTotalHardViolations(current.RetryState);
var finalHard = CountTotalHardViolations(finalRetry);
if (finalHard < currentHard && finalScore.NodeCrossings <= current.Score.NodeCrossings)
{
current = current with { Score = finalScore, RetryState = finalRetry, Edges = finalNormalized };
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after final normalization: {DescribeSolution(current)}");
}
}
// Targeted under-node elevation with net-total promotion.
// ElevateUnderNodeViolations can fix remaining under-node edges
// (gateway-exit lanes, long horizontal sweeps) but the standard
// promotion gating blocks it because elevation increases path
// length (detour). Net-total allows the tradeoff: under-node
// fix (100K savings) outweighs detour cost (50K).
if (current.RetryState.UnderNodeViolations > 0)
{
var elevated = ElkEdgePostProcessor.ElevateUnderNodeViolations(
current.Edges, nodes, minLineClearance);
elevated = ElkEdgePostProcessor.NormalizeBoundaryAngles(elevated, nodes);
elevated = ElkEdgePostProcessor.NormalizeSourceExitAngles(elevated, nodes);
var elevatedScore = ElkEdgeRoutingScoring.ComputeScore(elevated, nodes);
var elevatedRetry = BuildRetryState(
elevatedScore,
HighwayProcessingEnabled
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(elevated, nodes).Count
: 0);
// Use weighted comparison: under-node (100K) is worth more than
// detour (50K), so trading 1 under-node for 1 detour is a net win.
if (elevatedScore.Value > current.Score.Value
&& elevatedScore.NodeCrossings <= current.Score.NodeCrossings)
{
current = current with { Score = elevatedScore, RetryState = elevatedRetry, Edges = elevated };
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after under-node elevation: {DescribeSolution(current)}");
}
}
return current;
}
private static bool HasHybridHardRulePressure(RoutingRetryState retryState)
{
return retryState.RemainingShortHighways > 0
|| retryState.RepeatCollectorCorridorViolations > 0
|| retryState.RepeatCollectorNodeClearanceViolations > 0
|| retryState.TargetApproachJoinViolations > 0
|| retryState.TargetApproachBacktrackingViolations > 0
|| retryState.ExcessiveDetourViolations > 0
|| retryState.SharedLaneViolations > 0
|| retryState.BoundarySlotViolations > 0
|| retryState.BelowGraphViolations > 0
|| retryState.UnderNodeViolations > 0
|| retryState.EntryAngleViolations > 0
|| retryState.GatewaySourceExitViolations > 0;
}
}