using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Comparison; using MultiWheelC.TrajectoryPlanning.PathSmoothing.Processing; using MultiWheelC.TrajectoryPlanning.PathSmoothing.Validation; namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Facade; /// /// 原始路径基线与一次 Local G2 平滑的离线比较入口。 /// 此类仅汇总已冻结请求的质量、确定性摘要和耗时,不参与实时控制或修改平滑结果。 /// public sealed class PathSmoothingComparisonService { private readonly PathSmoothingService _smoothingService = new PathSmoothingService(); private readonly PathSmoothingPreprocessor _preprocessor = new PathSmoothingPreprocessor(); private readonly PathGeometryAnalyzer _analyzer = new PathGeometryAnalyzer(); private readonly SmoothedPathValidator _validator = new SmoothedPathValidator(); /// 创建原始基线并比较一次 Local G2 运行的质量、耗时和推荐资格。 /// 比较请求,包含待平滑的不可变请求快照和比较设置;为 时返回带失败基线的结果。 /// 取消比较和重复计时的调用方令牌。 /// 包含原始基线、候选条目和推荐方法的不可变比较结果;取消时 Cancelled public PathSmoothingComparisonResult Compare( PathSmoothingComparisonRequest request, CancellationToken cancellationToken = default) { PathSmoothingComparisonEntry baseline = CreateRawPathBaseline(request, out string baselineReason); var entries = new List(1); if (cancellationToken.IsCancellationRequested) return Cancelled(baseline, entries); if (request == null || request.SmoothingRequest == null) return new PathSmoothingComparisonResult(baseline, entries, null, false, baselineReason); if (!TryCompareLocalG2( request.SmoothingRequest, baseline.Metrics, cancellationToken, out PathSmoothingComparisonEntry entry)) { return Cancelled(baseline, entries); } entries.Add(entry); return new PathSmoothingComparisonResult( baseline, entries, entry.IsEligibleForRecommendation ? SmoothingMethod.LocalG2Quintic : null, false, baselineReason); } private bool TryCompareLocalG2( PathSmoothingRequest smoothingRequest, PathQualityMetrics rawMetrics, CancellationToken cancellationToken, out PathSmoothingComparisonEntry entry) { entry = null; try { cancellationToken.ThrowIfCancellationRequested(); PathSmoothingResult warmup = _smoothingService.Smooth(smoothingRequest, cancellationToken); if (warmup.Status == PathSmoothingStatus.Cancelled || cancellationToken.IsCancellationRequested) return false; var timings = new List(5); var measuredResults = new List(5); for (int sampleIndex = 0; sampleIndex < 5; sampleIndex++) { cancellationToken.ThrowIfCancellationRequested(); Stopwatch stopwatch = Stopwatch.StartNew(); PathSmoothingResult result = _smoothingService.Smooth(smoothingRequest, cancellationToken); stopwatch.Stop(); if (result.Status == PathSmoothingStatus.Cancelled || cancellationToken.IsCancellationRequested) return false; timings.Add(stopwatch.Elapsed.TotalMilliseconds); measuredResults.Add(result); } PathSmoothingResult canonical = measuredResults[0]; string digest = StableGeometryDigest.Compute(canonical); SmoothingTimingSummary timing = SmoothingTimingSummary.FromMeasurements(timings, measuredResults); string diagnostic = string.IsNullOrWhiteSpace(timing.Diagnostic) ? canonical.Diagnostics.TerminationReason : timing.Diagnostic; PathQualityMetrics metrics = PathSmoothingComparisonEntry.IsPublishedLocalG2Status(canonical.Status) ? NormalizeMetrics(canonical.Diagnostics.Metrics, rawMetrics) : new PathQualityMetrics(); entry = PathSmoothingComparisonEntry.CreateCandidate( SmoothingMethod.LocalG2Quintic, canonical.Status, metrics, timing, digest, diagnostic, canonical.Path, canonical.Segments); return true; } catch (OperationCanceledException) { return false; } catch (Exception exception) { entry = PathSmoothingComparisonEntry.CreateCandidate( SmoothingMethod.LocalG2Quintic, PathSmoothingStatus.Failed, new PathQualityMetrics(), new SmoothingTimingSummary(new double[] { 0d, 0d, 0d, 0d, 0d }, false, exception.GetType().Name), string.Empty, exception.Message, null, null); return true; } } private PathSmoothingComparisonEntry CreateRawPathBaseline( PathSmoothingComparisonRequest request, out string reason) { reason = string.Empty; if (request == null || request.SmoothingRequest == null) return FailedBaseline(PathSmoothingStatus.InvalidInput, "Comparison request is required.", out reason); PathSmoothingRequest smoothingRequest = request.SmoothingRequest; PathSmoothingConfiguration configuration = smoothingRequest.Configuration; if (configuration == null || smoothingRequest.Map == null || smoothingRequest.Vehicle == null) { return FailedBaseline( PathSmoothingStatus.InvalidInput, "A planning-ready map, vehicle, and configuration are required.", out reason); } if (!_preprocessor.TryPrepare(smoothingRequest, out PreparedPath preparedPath, out reason)) return FailedBaseline(PathSmoothingStatus.InvalidInput, reason, out reason); if (!RawPathBaselineBuilder.TryCreate( smoothingRequest, preparedPath, _analyzer, configuration.OutputSpacingMeters, _validator, configuration.MaximumCollisionCheckStepMeters, out RawPathBaseline rawPath, out reason)) { return FailedBaseline(PathSmoothingStatus.InvalidInput, reason, out reason); } string digest = StableGeometryDigest.Compute(PathSmoothingStatus.NotNeeded, null, rawPath.Path, rawPath.Segments); return PathSmoothingComparisonEntry.CreateRawPathBaseline( PathSmoothingStatus.NotNeeded, rawPath.Metrics, digest, string.Empty, rawPath.Path, rawPath.Segments); } private static PathSmoothingComparisonEntry FailedBaseline( PathSmoothingStatus status, string failureReason, out string reason) { reason = failureReason ?? string.Empty; return PathSmoothingComparisonEntry.CreateRawPathBaseline( status, new PathQualityMetrics(), StableGeometryDigest.Compute(status, null, null, null), reason, null, null); } private static PathSmoothingComparisonResult Cancelled( PathSmoothingComparisonEntry baseline, IReadOnlyList entries) { return new PathSmoothingComparisonResult(baseline, entries, null, true, "Path smoothing comparison was cancelled."); } private static PathQualityMetrics NormalizeMetrics( PathQualityMetrics candidate, PathQualityMetrics raw) { if (candidate == null || raw == null || !candidate.IsFeasible) return new PathQualityMetrics(); return new PathQualityMetrics( true, candidate.PathLengthMeters, candidate.MaximumAbsoluteVehicleCurvaturePerMeter, candidate.MaximumAbsoluteVehicleCurvatureDerivativePerSquareMeter, candidate.RootMeanSquareVehicleCurvaturePerMeter, candidate.TotalAbsoluteCurvatureVariationPerMeter, candidate.CurvatureVariationEnergy, candidate.MinimumBodyClearanceMeters, RelativePercentOrAbsoluteDelta(candidate.PathLengthMeters, raw.PathLengthMeters), RelativePercentOrAbsoluteDelta( candidate.MaximumAbsoluteVehicleCurvaturePerMeter, raw.MaximumAbsoluteVehicleCurvaturePerMeter), RelativePercentOrAbsoluteDelta( candidate.TotalAbsoluteCurvatureVariationPerMeter, raw.TotalAbsoluteCurvatureVariationPerMeter), candidate.MinimumBodyClearanceMeters - raw.MinimumBodyClearanceMeters); } private static double RelativePercentOrAbsoluteDelta(double candidate, double raw) { double delta = candidate - raw; return Math.Abs(raw) < 1e-12d ? delta : delta / raw * 100d; } }