fix: treat near-zero start as stopped in full-direction schedule

This commit is contained in:
梁薄云
2026-08-07 16:51:11 +08:00
parent 2d406359d0
commit 0d4e1aeb47
2 changed files with 32 additions and 3 deletions
@@ -46,7 +46,11 @@ public sealed class FullDirectionSegmentScheduleBuilder
int stationCount = speedLimit.PathS.Count; int stationCount = speedLimit.PathS.Count;
var speeds = new double[stationCount]; var speeds = new double[stationCount];
double desired = Math.Min(desiredSpeedMetersPerSecond, speedLimit.DirectionMaximumSpeedMetersPerSecond); double desired = Math.Min(desiredSpeedMetersPerSecond, speedLimit.DirectionMaximumSpeedMetersPerSecond);
speeds[0] = Math.Min(initialProgressSpeedMetersPerSecond, Math.Min(desired, double effectiveInitialSpeed = initialProgressSpeedMetersPerSecond <=
longitudinal.StopSpeedToleranceMetersPerSecond
? 0d
: initialProgressSpeedMetersPerSecond;
speeds[0] = Math.Min(effectiveInitialSpeed, Math.Min(desired,
speedLimit.MaximumSpeedMetersPerSecond[0])); speedLimit.MaximumSpeedMetersPerSecond[0]));
for (int index = 1; index < stationCount; index++) for (int index = 1; index < stationCount; index++)
{ {
@@ -100,7 +104,7 @@ public sealed class FullDirectionSegmentScheduleBuilder
referenceSpeeds[referenceSpeeds.Count - 1] = 0d; referenceSpeeds[referenceSpeeds.Count - 1] = 0d;
pathS[pathS.Count - 1] = speedLimit.PathUpperBoundS; pathS[pathS.Count - 1] = speedLimit.PathUpperBoundS;
EnsureJerkReachableReferenceTimes(times, referenceSpeeds, longitudinal); EnsureJerkReachableReferenceTimes(times, referenceSpeeds, longitudinal);
EnsureMinimumExactStopDuration(times, speedLimit.PathUpperBoundS, initialProgressSpeedMetersPerSecond, EnsureMinimumExactStopDuration(times, speedLimit.PathUpperBoundS, effectiveInitialSpeed,
initialAccelerationMetersPerSecondSquared, longitudinal); initialAccelerationMetersPerSecondSquared, longitudinal);
if (!IsFinite(longitudinal.ZeroSpeedHoldSeconds) || longitudinal.ZeroSpeedHoldSeconds < 0d) if (!IsFinite(longitudinal.ZeroSpeedHoldSeconds) || longitudinal.ZeroSpeedHoldSeconds < 0d)
{ {
@@ -139,7 +143,8 @@ public sealed class FullDirectionSegmentScheduleBuilder
private static void EnsureMinimumExactStopDuration(IList<double> times, double stopBoundaryPathS, private static void EnsureMinimumExactStopDuration(IList<double> times, double stopBoundaryPathS,
double initialSpeed, double initialAcceleration, LongitudinalConfiguration configuration) double initialSpeed, double initialAcceleration, LongitudinalConfiguration configuration)
{ {
if (initialSpeed <= Tolerance || !JerkLimitedStoppingMath.TryCalculate(initialSpeed, initialAcceleration, if (initialSpeed <= configuration.StopSpeedToleranceMetersPerSecond ||
!JerkLimitedStoppingMath.TryCalculate(initialSpeed, initialAcceleration,
configuration.MaximumDecelerationMetersPerSecondSquared, configuration.MaximumDecelerationMetersPerSecondSquared,
configuration.MaximumJerkMetersPerSecondCubed, out JerkLimitedStoppingProfile stop, out _)) configuration.MaximumJerkMetersPerSecondCubed, out JerkLimitedStoppingProfile stop, out _))
{ {
@@ -17,6 +17,7 @@ internal static class LongitudinalIntegrationChecks
VerifiesFullDirectionStaticStartMakesProgress(); VerifiesFullDirectionStaticStartMakesProgress();
VerifiesFullDirectionNearZeroSpeedUsesStaticStartSeed(); VerifiesFullDirectionNearZeroSpeedUsesStaticStartSeed();
VerifiesFullDirectionCurvedNearZeroStartUsesStaticStartSeed(); VerifiesFullDirectionCurvedNearZeroStartUsesStaticStartSeed();
VerifiesFullDirectionNearZeroScheduleDurationIsReasonable();
VerifiesFullDirectionPublicationDoesNotDuplicateItsTerminalHold(); VerifiesFullDirectionPublicationDoesNotDuplicateItsTerminalHold();
VerifiesExactStopIncludesAStabilizationTail(); VerifiesExactStopIncludesAStabilizationTail();
VerifiesLastStrictCandidateSurvivesLaterTimeout(); VerifiesLastStrictCandidateSurvivesLaterTimeout();
@@ -224,6 +225,29 @@ internal static class LongitudinalIntegrationChecks
"curved near-zero start stops at the terminal boundary"); "curved near-zero start stops at the terminal boundary");
} }
private static void VerifiesFullDirectionNearZeroScheduleDurationIsReasonable()
{
EmPlannerConfiguration configuration = EmPlannerConfiguration.CreateDefault();
LateralPath path = new LateralPath(new[]
{
Point(0d, 0d, 0d),
Point(1d, 2.25d, 0.1d),
Point(2d, 4.5d, 0d),
}, true);
EmPlanningStatus status = new PathSpeedLimitBuilder().Build(path, TravelDirection.Forward, 0.0001d,
EmTerminalType.Goal, configuration, out PathSpeedLimit speedLimit, out string failureReason);
Verification.Equal(EmPlanningStatus.Success, status, "near-zero duration envelope: " + failureReason);
status = new FullDirectionSegmentScheduleBuilder().TryBuild(path, speedLimit, 0.0001d, 0d,
configuration.Longitudinal.DesiredForwardSpeedMetersPerSecond, configuration,
out LongitudinalKnotSchedule schedule, out failureReason);
Verification.Equal(EmPlanningStatus.Success, status, "near-zero duration schedule: " + failureReason);
double duration = schedule.KnotTimes[schedule.KnotTimes.Count - 1];
Verification.True(duration < 120d,
"near-zero schedule duration is normal: " + duration.ToString("R", System.Globalization.CultureInfo.InvariantCulture));
}
private static void VerifiesFullDirectionPublicationDoesNotDuplicateItsTerminalHold() private static void VerifiesFullDirectionPublicationDoesNotDuplicateItsTerminalHold()
{ {
EmPlannerConfiguration configuration = EmPlannerConfiguration.CreateDefault(); EmPlannerConfiguration configuration = EmPlannerConfiguration.CreateDefault();