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>
138 lines
6.9 KiB
C#
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;
|
|
}
|
|
}
|