Files
ParkingRobot/ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingComparisonService.cs
T

219 lines
9.4 KiB
C#
Raw Normal View History

2026-07-29 15:30:50 +08:00
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
2026-08-09 22:13:18 +08:00
using MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Comparison;
2026-07-29 15:30:50 +08:00
using MultiWheelC.TrajectoryPlanning.PathSmoothing.Processing;
using MultiWheelC.TrajectoryPlanning.PathSmoothing.Validation;
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Facade;
2026-08-09 22:13:18 +08:00
/// <summary>
/// 原始路径基线与一次 Local G2 平滑的离线比较入口。
/// 此类仅汇总已冻结请求的质量、确定性摘要和耗时,不参与实时控制或修改平滑结果。
/// </summary>
2026-07-29 15:30:50 +08:00
public sealed class PathSmoothingComparisonService
{
private readonly PathSmoothingService _smoothingService = new PathSmoothingService();
private readonly PathSmoothingPreprocessor _preprocessor = new PathSmoothingPreprocessor();
2026-07-30 16:39:08 +08:00
private readonly PathGeometryAnalyzer _analyzer = new PathGeometryAnalyzer();
2026-07-29 15:30:50 +08:00
private readonly SmoothedPathValidator _validator = new SmoothedPathValidator();
2026-08-09 22:13:18 +08:00
/// <summary>创建原始基线并比较一次 Local G2 运行的质量、耗时和推荐资格。</summary>
/// <param name="request">比较请求,包含待平滑的不可变请求快照和比较设置;为 <see langword="null"/> 时返回带失败基线的结果。</param>
/// <param name="cancellationToken">取消比较和重复计时的调用方令牌。</param>
/// <returns>包含原始基线、候选条目和推荐方法的不可变比较结果;取消时 <c>Cancelled</c> 为 <see langword="true"/>。</returns>
2026-07-29 15:30:50 +08:00
public PathSmoothingComparisonResult Compare(
PathSmoothingComparisonRequest request,
CancellationToken cancellationToken = default)
{
PathSmoothingComparisonEntry baseline = CreateRawPathBaseline(request, out string baselineReason);
2026-08-09 22:13:18 +08:00
var entries = new List<PathSmoothingComparisonEntry>(1);
2026-07-29 15:30:50 +08:00
if (cancellationToken.IsCancellationRequested)
return Cancelled(baseline, entries);
if (request == null || request.SmoothingRequest == null)
return new PathSmoothingComparisonResult(baseline, entries, null, false, baselineReason);
2026-08-09 22:13:18 +08:00
if (!TryCompareLocalG2(
request.SmoothingRequest,
baseline.Metrics,
cancellationToken,
out PathSmoothingComparisonEntry entry))
2026-07-29 15:30:50 +08:00
{
2026-08-09 22:13:18 +08:00
return Cancelled(baseline, entries);
2026-07-29 15:30:50 +08:00
}
2026-08-09 22:13:18 +08:00
entries.Add(entry);
2026-07-29 15:30:50 +08:00
return new PathSmoothingComparisonResult(
baseline,
entries,
2026-08-09 22:13:18 +08:00
entry.IsEligibleForRecommendation ? SmoothingMethod.LocalG2Quintic : null,
2026-07-29 15:30:50 +08:00
false,
baselineReason);
}
2026-08-09 22:13:18 +08:00
private bool TryCompareLocalG2(
PathSmoothingRequest smoothingRequest,
2026-07-29 15:30:50 +08:00
PathQualityMetrics rawMetrics,
CancellationToken cancellationToken,
out PathSmoothingComparisonEntry entry)
{
entry = null;
try
{
cancellationToken.ThrowIfCancellationRequested();
2026-08-09 22:13:18 +08:00
PathSmoothingResult warmup = _smoothingService.Smooth(smoothingRequest, cancellationToken);
2026-07-29 15:30:50 +08:00
if (warmup.Status == PathSmoothingStatus.Cancelled || cancellationToken.IsCancellationRequested) return false;
var timings = new List<double>(5);
var measuredResults = new List<PathSmoothingResult>(5);
for (int sampleIndex = 0; sampleIndex < 5; sampleIndex++)
{
cancellationToken.ThrowIfCancellationRequested();
Stopwatch stopwatch = Stopwatch.StartNew();
2026-08-09 22:13:18 +08:00
PathSmoothingResult result = _smoothingService.Smooth(smoothingRequest, cancellationToken);
2026-07-29 15:30:50 +08:00
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;
2026-08-09 22:13:18 +08:00
PathQualityMetrics metrics = PathSmoothingComparisonEntry.IsPublishedLocalG2Status(canonical.Status)
2026-07-29 15:30:50 +08:00
? NormalizeMetrics(canonical.Diagnostics.Metrics, rawMetrics)
: new PathQualityMetrics();
entry = PathSmoothingComparisonEntry.CreateCandidate(
2026-08-09 22:13:18 +08:00
SmoothingMethod.LocalG2Quintic,
2026-07-29 15:30:50 +08:00
canonical.Status,
metrics,
timing,
digest,
diagnostic,
canonical.Path,
canonical.Segments);
return true;
}
catch (OperationCanceledException)
{
return false;
}
catch (Exception exception)
{
entry = PathSmoothingComparisonEntry.CreateCandidate(
2026-08-09 22:13:18 +08:00
SmoothingMethod.LocalG2Quintic,
2026-07-29 15:30:50 +08:00
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)
2026-08-09 22:13:18 +08:00
return FailedBaseline(PathSmoothingStatus.InvalidInput, "Comparison request is required.", out reason);
2026-07-29 15:30:50 +08:00
PathSmoothingRequest smoothingRequest = request.SmoothingRequest;
PathSmoothingConfiguration configuration = smoothingRequest.Configuration;
if (configuration == null || smoothingRequest.Map == null || smoothingRequest.Vehicle == null)
2026-08-09 22:13:18 +08:00
{
return FailedBaseline(
PathSmoothingStatus.InvalidInput,
"A planning-ready map, vehicle, and configuration are required.",
out reason);
}
2026-07-29 15:30:50 +08:00
if (!_preprocessor.TryPrepare(smoothingRequest, out PreparedPath preparedPath, out reason))
return FailedBaseline(PathSmoothingStatus.InvalidInput, reason, out reason);
2026-07-30 08:42:14 +08:00
if (!RawPathBaselineBuilder.TryCreate(
smoothingRequest,
2026-07-29 15:30:50 +08:00
preparedPath,
2026-07-30 16:39:08 +08:00
_analyzer,
configuration.OutputSpacingMeters,
2026-07-30 08:42:14 +08:00
_validator,
2026-07-29 15:30:50 +08:00
configuration.MaximumCollisionCheckStepMeters,
2026-07-30 08:42:14 +08:00
out RawPathBaseline rawPath,
2026-07-29 15:30:50 +08:00
out reason))
2026-08-09 22:13:18 +08:00
{
2026-07-29 15:30:50 +08:00
return FailedBaseline(PathSmoothingStatus.InvalidInput, reason, out reason);
2026-08-09 22:13:18 +08:00
}
2026-07-29 15:30:50 +08:00
2026-08-09 22:13:18 +08:00
string digest = StableGeometryDigest.Compute(PathSmoothingStatus.NotNeeded, null, rawPath.Path, rawPath.Segments);
2026-07-29 15:30:50 +08:00
return PathSmoothingComparisonEntry.CreateRawPathBaseline(
2026-08-09 22:13:18 +08:00
PathSmoothingStatus.NotNeeded,
2026-07-30 08:42:14 +08:00
rawPath.Metrics,
2026-07-29 15:30:50 +08:00
digest,
string.Empty,
2026-07-30 08:42:14 +08:00
rawPath.Path,
rawPath.Segments);
2026-07-29 15:30:50 +08:00
}
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<PathSmoothingComparisonEntry> entries)
{
2026-08-09 22:13:18 +08:00
return new PathSmoothingComparisonResult(baseline, entries, null, true, "Path smoothing comparison was cancelled.");
2026-07-29 15:30:50 +08:00
}
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,
2026-07-30 16:39:08 +08:00
candidate.MaximumAbsoluteVehicleCurvatureDerivativePerSquareMeter,
2026-07-29 15:30:50 +08:00
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;
}
}