Refactor ElkSharp hybrid routing and document speed path

This commit is contained in:
master
2026-03-29 19:33:46 +03:00
parent 7d6bc2b0ab
commit e8f7ad7652
89 changed files with 13280 additions and 10732 deletions

View File

@@ -0,0 +1,644 @@
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 = 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);
var repairSeedScore = (EdgeRoutingScore?)null;
ElkRoutedEdge[]? repairSeedEdges = null;
var repairSeedRetryState = 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);
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 = 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);
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;
}
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;
}
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));
}
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;
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)
{
outcome = $"stalled-same-focus({DescribeRetryState(retryState)})@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
break;
}
}
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)
{
outcome = $"stalled-repeat({DescribeRetryState(retryState)})@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
break;
}
var blockingCycleFingerprint = BuildBlockingCycleFingerprint(retryState, repairPlan);
if (ShouldStopForBlockingCycle(
recentBlockingCycleFingerprints,
blockingCycleFingerprint,
retryState,
bestAttemptRetryState,
attempt))
{
outcome = $"stalled-cycle({DescribeRetryState(retryState)})@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
break;
}
AppendRecentFingerprint(recentBlockingCycleFingerprints, blockingCycleFingerprint, 4);
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;
if (ShouldStopForStagnation(stagnantAttempts, attempt, config.MaxAdaptationsPerStrategy))
{
outcome = $"stalled({DescribeRetryState(retryState)})@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
break;
}
}
else
{
stagnantAttempts++;
if (ShouldStopForStagnation(stagnantAttempts, attempt, config.MaxAdaptationsPerStrategy))
{
outcome = $"stalled({DescribeRetryState(retryState)})@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
break;
}
}
var attemptOutcome = score.NodeCrossings > maxAllowedNodeCrossings
? $"hard-violation(nc={score.NodeCrossings}>{maxAllowedNodeCrossings})"
: retryState.RequiresPrimaryRetry
? $"retry({DescribeRetryState(retryState)})"
: ShouldRetryForEdgeCrossings(retryState, attempt, config.MaxAdaptationsPerStrategy)
? $"retry(edge-crossings={retryState.EdgeCrossings})"
: "valid";
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 (score.NodeCrossings > maxAllowedNodeCrossings)
{
outcome = $"{attemptOutcome}@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting after node crossing violation");
strategy.AdaptForViolations(score, attempt, retryState);
continue;
}
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, config.MaxAdaptationsPerStrategy))
{
outcome = $"{attemptOutcome}@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting for blocking violations");
strategy.AdaptForViolations(score, attempt, retryState);
continue;
}
outcome = $"invalid({DescribeRetryState(retryState)})@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
break;
}
if (retryState.RequiresLengthRetry)
{
if (ShouldRetryForPrimaryViolations(retryState, attempt, config.MaxAdaptationsPerStrategy))
{
outcome = $"{attemptOutcome}@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting for shortest-path / detour violations");
strategy.AdaptForViolations(score, attempt, retryState);
continue;
}
outcome = $"invalid({DescribeRetryState(retryState)})@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
break;
}
if (retryState.RequiresQualityRetry)
{
if (ShouldRetryForPrimaryViolations(retryState, attempt, config.MaxAdaptationsPerStrategy))
{
outcome = $"{attemptOutcome}@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting for quality/length violations");
strategy.AdaptForViolations(score, attempt, retryState);
continue;
}
}
if (ShouldRetryForEdgeCrossings(retryState, attempt, config.MaxAdaptationsPerStrategy))
{
outcome = $"{attemptOutcome}@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting for edge crossings");
strategy.AdaptForViolations(score, attempt, retryState);
continue;
}
var residualSoftViolations = retryState.RequiresQualityRetry || retryState.EdgeCrossings > 0;
outcome = residualSoftViolations
? $"valid-soft({DescribeRetryState(retryState)})@attempt{attempt + 1}"
: $"valid@attempt{attempt + 1}";
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
validSolution = candidate;
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);
}
}