fix: normalize EM PathS resampling residuals

This commit is contained in:
梁薄云
2026-08-10 14:24:15 +08:00
parent 8a1ba2a6d5
commit 06d9037645
2 changed files with 43 additions and 11 deletions
@@ -9,6 +9,7 @@ public sealed class EmTrajectoryAssembler
{ {
private readonly double outputTimeStepSeconds; private readonly double outputTimeStepSeconds;
private readonly double zeroSpeedHoldSeconds; private readonly double zeroSpeedHoldSeconds;
private readonly double pathSTolerance;
private readonly int maximumPublishedSampleCount; private readonly int maximumPublishedSampleCount;
public EmTrajectoryAssembler() public EmTrajectoryAssembler()
@@ -18,13 +19,16 @@ public sealed class EmTrajectoryAssembler
public EmTrajectoryAssembler(EmPlannerConfiguration configuration) public EmTrajectoryAssembler(EmPlannerConfiguration configuration)
{ {
if (configuration == null || configuration.Scheduling == null || configuration.Longitudinal == null) if (configuration == null || configuration.Scheduling == null || configuration.Longitudinal == null ||
configuration.Validation == null)
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
outputTimeStepSeconds = configuration.Scheduling.OutputTimeStepSeconds; outputTimeStepSeconds = configuration.Scheduling.OutputTimeStepSeconds;
zeroSpeedHoldSeconds = configuration.Longitudinal.ZeroSpeedHoldSeconds; zeroSpeedHoldSeconds = configuration.Longitudinal.ZeroSpeedHoldSeconds;
pathSTolerance = configuration.Validation.KinematicTolerance;
maximumPublishedSampleCount = configuration.Scheduling.MaximumPublishedSampleCount; maximumPublishedSampleCount = configuration.Scheduling.MaximumPublishedSampleCount;
if (!IsFinite(outputTimeStepSeconds) || outputTimeStepSeconds <= 0d || !IsFinite(zeroSpeedHoldSeconds) || if (!IsFinite(outputTimeStepSeconds) || outputTimeStepSeconds <= 0d || !IsFinite(zeroSpeedHoldSeconds) ||
zeroSpeedHoldSeconds < 0d || maximumPublishedSampleCount < 2) zeroSpeedHoldSeconds < 0d || !IsFinite(pathSTolerance) || pathSTolerance < 0d ||
maximumPublishedSampleCount < 2)
{ {
throw new ArgumentOutOfRangeException(nameof(configuration)); throw new ArgumentOutOfRangeException(nameof(configuration));
} }
@@ -64,7 +68,7 @@ public sealed class EmTrajectoryAssembler
return EmPlanningStatus.FullSegmentResourceLimitExceeded; return EmPlanningStatus.FullSegmentResourceLimitExceeded;
} }
var schedule = new TrajectorySampleSchedule(longitudinal.Candidate, outputTimeStepSeconds, holdDurationSeconds, var schedule = new TrajectorySampleSchedule(longitudinal.Candidate, outputTimeStepSeconds, holdDurationSeconds,
metadata.LongitudinalMode, isFullDirectionSegment); metadata.LongitudinalMode, isFullDirectionSegment, pathSTolerance);
double terminalPathS = path.Points[path.Points.Count - 1].PathS; double terminalPathS = path.Points[path.Points.Count - 1].PathS;
var points = new List<EmTrajectoryPoint>(schedule.Samples.Count); var points = new List<EmTrajectoryPoint>(schedule.Samples.Count);
double directionSign = metadata.Direction == TravelDirection.Forward ? 1d : -1d; double directionSign = metadata.Direction == TravelDirection.Forward ? 1d : -1d;
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Globalization;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
@@ -9,7 +10,7 @@ internal sealed class TrajectorySampleSchedule
private const double ZeroTolerance = 1e-12d; private const double ZeroTolerance = 1e-12d;
public TrajectorySampleSchedule(LongitudinalCandidate candidate, double outputTimeStepSeconds, double holdDurationSeconds, public TrajectorySampleSchedule(LongitudinalCandidate candidate, double outputTimeStepSeconds, double holdDurationSeconds,
EmLongitudinalMode mode, bool resampleMotion) EmLongitudinalMode mode, bool resampleMotion, double pathSTolerance)
{ {
if (candidate == null) if (candidate == null)
throw new ArgumentNullException(nameof(candidate)); throw new ArgumentNullException(nameof(candidate));
@@ -17,11 +18,14 @@ internal sealed class TrajectorySampleSchedule
throw new ArgumentOutOfRangeException(nameof(outputTimeStepSeconds)); throw new ArgumentOutOfRangeException(nameof(outputTimeStepSeconds));
if (!IsFinite(holdDurationSeconds) || holdDurationSeconds < 0d) if (!IsFinite(holdDurationSeconds) || holdDurationSeconds < 0d)
throw new ArgumentOutOfRangeException(nameof(holdDurationSeconds)); throw new ArgumentOutOfRangeException(nameof(holdDurationSeconds));
if (!IsFinite(pathSTolerance) || pathSTolerance < 0d)
throw new ArgumentOutOfRangeException(nameof(pathSTolerance));
if (!Enum.IsDefined(typeof(EmLongitudinalMode), mode)) if (!Enum.IsDefined(typeof(EmLongitudinalMode), mode))
throw new ArgumentOutOfRangeException(nameof(mode)); throw new ArgumentOutOfRangeException(nameof(mode));
var samples = new List<TrajectorySample>(candidate.KnotTimes.Count + 4); var samples = new List<TrajectorySample>(candidate.KnotTimes.Count + 4);
double previousPathS = double.NegativeInfinity; double previousPathS = double.NegativeInfinity;
double terminalPathS = candidate.S[candidate.S.Count - 1];
if (resampleMotion) if (resampleMotion)
{ {
double finalTime = candidate.KnotTimes[candidate.KnotTimes.Count - 1]; double finalTime = candidate.KnotTimes[candidate.KnotTimes.Count - 1];
@@ -29,9 +33,11 @@ internal sealed class TrajectorySampleSchedule
for (double sampleTime = 0d; sampleTime < finalTime - ZeroTolerance; for (double sampleTime = 0d; sampleTime < finalTime - ZeroTolerance;
sampleTime += outputTimeStepSeconds) sampleTime += outputTimeStepSeconds)
{ {
AddSample(Interpolate(candidate, sampleTime, ref sourceInterval), samples, ref previousPathS, candidate); AddSample(Interpolate(candidate, sampleTime, ref sourceInterval), samples.Count, samples,
ref previousPathS, terminalPathS, pathSTolerance, candidate);
} }
AddSample(Interpolate(candidate, finalTime, ref sourceInterval), samples, ref previousPathS, candidate); AddSample(Interpolate(candidate, finalTime, ref sourceInterval), samples.Count, samples,
ref previousPathS, terminalPathS, pathSTolerance, candidate);
} }
else else
{ {
@@ -39,7 +45,7 @@ internal sealed class TrajectorySampleSchedule
{ {
AddSample(new TrajectorySample(candidate.KnotTimes[index], candidate.S[index], AddSample(new TrajectorySample(candidate.KnotTimes[index], candidate.S[index],
Math.Max(0d, candidate.U[index]), candidate.A[index], index < candidate.J.Count ? candidate.J[index] : 0d, Math.Max(0d, candidate.U[index]), candidate.A[index], index < candidate.J.Count ? candidate.J[index] : 0d,
false), samples, ref previousPathS, candidate); false), samples.Count, samples, ref previousPathS, terminalPathS, pathSTolerance, candidate);
} }
} }
@@ -130,17 +136,39 @@ internal sealed class TrajectorySampleSchedule
return false; return false;
} }
private static void AddSample(TrajectorySample sample, ICollection<TrajectorySample> samples, private static void AddSample(TrajectorySample sample, int sampleIndex, ICollection<TrajectorySample> samples,
ref double previousPathS, LongitudinalCandidate candidate) ref double previousPathS, double terminalPathS, double pathSTolerance, LongitudinalCandidate candidate)
{ {
if (sample.PathS < previousPathS) double lowerDifference = previousPathS - sample.PathS;
throw new ArgumentException("Trajectory PathS cannot decrease.", nameof(candidate)); double upperDifference = sample.PathS - terminalPathS;
if (lowerDifference > pathSTolerance || upperDifference > pathSTolerance)
{
throw PathSFailure(sampleIndex, previousPathS, sample.PathS,
Math.Max(lowerDifference, upperDifference), candidate);
}
double normalizedPathS = Math.Min(terminalPathS, Math.Max(previousPathS, sample.PathS));
if (normalizedPathS != sample.PathS)
{
sample = new TrajectorySample(sample.TimeFromStart, normalizedPathS, sample.ProgressSpeed,
sample.Acceleration, sample.Jerk, sample.IsHoldSample);
}
if (sample.ProgressSpeed < -ZeroTolerance) if (sample.ProgressSpeed < -ZeroTolerance)
throw new ArgumentException("Longitudinal progress speed cannot be negative.", nameof(candidate)); throw new ArgumentException("Longitudinal progress speed cannot be negative.", nameof(candidate));
samples.Add(sample); samples.Add(sample);
previousPathS = sample.PathS; previousPathS = sample.PathS;
} }
private static ArgumentException PathSFailure(int sampleIndex, double previousPathS,
double candidatePathS, double difference, LongitudinalCandidate candidate)
{
return new ArgumentException("Trajectory PathS violates monotonic publication bounds: sampleIndex=" +
sampleIndex.ToString(CultureInfo.InvariantCulture) + ";previousPathS=" +
previousPathS.ToString("R", CultureInfo.InvariantCulture) + ";candidatePathS=" +
candidatePathS.ToString("R", CultureInfo.InvariantCulture) + ";difference=" +
difference.ToString("R", CultureInfo.InvariantCulture) + ".", nameof(candidate));
}
private static bool IncrementAndExceedsMaximum(ref int sampleCount, int maximumSampleCount) private static bool IncrementAndExceedsMaximum(ref int sampleCount, int maximumSampleCount)
{ {
checked { sampleCount++; } checked { sampleCount++; }