using System; using MultiWheelC.TrajectoryPlanning.CoarsePath; namespace MultiWheelC.TrajectoryPlanning.EMPlanner; /// Reference-distance terminal chosen before LS without crossing the current direction segment. public sealed class PlanningHorizonSelection { internal PlanningHorizonSelection(double terminalReferenceS, EmTerminalType terminalType) { TerminalReferenceS = terminalReferenceS; TerminalType = terminalType; } public double TerminalReferenceS { get; } public EmTerminalType TerminalType { get; } } public sealed class PlanningHorizonSelector { private const double BoundaryTolerance = 1e-8d; public EmPlanningStatus Select(DirectionSegmentView segment, double currentSegmentReferenceS, double initialProgressSpeedMetersPerSecond, double initialAccelerationMetersPerSecondSquared, EmPlannerConfiguration configuration, out PlanningHorizonSelection selection, out string failureReason) { selection = null; failureReason = string.Empty; if (segment == null || configuration == null || configuration.Scheduling == null || configuration.Longitudinal == null || !IsFinite(currentSegmentReferenceS) || currentSegmentReferenceS < 0d || currentSegmentReferenceS > segment.LengthMeters + BoundaryTolerance || !IsFinite(initialProgressSpeedMetersPerSecond) || initialProgressSpeedMetersPerSecond < 0d || !IsFinite(initialAccelerationMetersPerSecondSquared)) { failureReason = "Planning horizon inputs are invalid."; return EmPlanningStatus.InvalidInput; } LongitudinalConfiguration longitudinal = configuration.Longitudinal; SchedulingConfiguration scheduling = configuration.Scheduling; double directionMaximum = segment.Direction == TravelDirection.Forward ? longitudinal.MaximumForwardSpeedMetersPerSecond : longitudinal.MaximumReverseSpeedMetersPerSecond; if (!IsPositiveFinite(directionMaximum) || !IsPositiveFinite(longitudinal.MaximumAccelerationMetersPerSecondSquared) || !IsPositiveFinite(longitudinal.MaximumDecelerationMetersPerSecondSquared) || !IsPositiveFinite(longitudinal.MaximumJerkMetersPerSecondCubed) || !IsPositiveFinite(longitudinal.ZeroSpeedHoldSeconds) || !IsPositiveFinite(scheduling.TimeHorizonSeconds) || !IsPositiveFinite(scheduling.DistanceHorizonMeters)) { failureReason = "Planning horizon configuration is invalid."; return EmPlanningStatus.InvalidInput; } if (initialProgressSpeedMetersPerSecond > directionMaximum + BoundaryTolerance || initialAccelerationMetersPerSecondSquared < -longitudinal.MaximumDecelerationMetersPerSecondSquared - BoundaryTolerance || initialAccelerationMetersPerSecondSquared > longitudinal.MaximumAccelerationMetersPerSecondSquared + BoundaryTolerance) { failureReason = "The initial state violates longitudinal bounds."; return EmPlanningStatus.InvalidInput; } double remainingSegment = Math.Max(0d, segment.LengthMeters - currentSegmentReferenceS); if (!JerkLimitedStoppingMath.TryCalculate(initialProgressSpeedMetersPerSecond, initialAccelerationMetersPerSecondSquared, longitudinal.MaximumDecelerationMetersPerSecondSquared, longitudinal.MaximumJerkMetersPerSecondCubed, out JerkLimitedStoppingProfile initialStop, out failureReason)) { return EmPlanningStatus.InvalidInput; } if (initialStop.DistanceMeters + BoundaryTolerance > remainingSegment) { failureReason = "The current segment lacks the jerk-limited stopping distance."; return EmPlanningStatus.StoppingDistanceInsufficient; } double timeReachable = CalculateReachableDistance(initialProgressSpeedMetersPerSecond, initialAccelerationMetersPerSecondSquared, directionMaximum, longitudinal, scheduling.TimeHorizonSeconds); double terminalReferenceS = currentSegmentReferenceS + Math.Min(remainingSegment, Math.Min(scheduling.DistanceHorizonMeters, timeReachable)); if (terminalReferenceS >= segment.LengthMeters - BoundaryTolerance) { terminalReferenceS = segment.LengthMeters; selection = new PlanningHorizonSelection(terminalReferenceS, ToTerminalType(segment.EndBoundary.BoundaryType)); } else { selection = new PlanningHorizonSelection(terminalReferenceS, EmTerminalType.RollingSafetyStop); } return EmPlanningStatus.Success; } private static double CalculateReachableDistance(double initialSpeed, double initialAcceleration, double maximumSpeed, LongitudinalConfiguration configuration, double timeHorizonSeconds) { if (!JerkLimitedStoppingMath.TryCalculate(maximumSpeed, 0d, configuration.MaximumDecelerationMetersPerSecondSquared, configuration.MaximumJerkMetersPerSecondCubed, out JerkLimitedStoppingProfile stopAtMaximumSpeed, out _)) { throw new ArgumentOutOfRangeException(nameof(configuration)); } double drivingDuration = timeHorizonSeconds - configuration.ZeroSpeedHoldSeconds - stopAtMaximumSpeed.DurationSeconds; if (drivingDuration <= 0d) return Math.Min(initialSpeed, maximumSpeed) * Math.Max(0d, timeHorizonSeconds - configuration.ZeroSpeedHoldSeconds); double speed = initialSpeed; double acceleration = initialAcceleration; double distance = 0d; const double simulationStepSeconds = 0.001d; while (drivingDuration > 0d) { double step = Math.Min(simulationStepSeconds, drivingDuration); double jerk = ChooseAccelerationJerk(speed, acceleration, maximumSpeed, configuration.MaximumAccelerationMetersPerSecondSquared, configuration.MaximumJerkMetersPerSecondCubed); double nextSpeed = speed + acceleration * step + 0.5d * jerk * step * step; if (nextSpeed > maximumSpeed) { nextSpeed = maximumSpeed; acceleration = 0d; jerk = 0d; } distance += speed * step + 0.5d * acceleration * step * step + jerk * step * step * step / 6d; acceleration += jerk * step; speed = Math.Max(0d, nextSpeed); drivingDuration -= step; } return Math.Max(0d, distance + stopAtMaximumSpeed.DistanceMeters); } private static double ChooseAccelerationJerk(double speed, double acceleration, double maximumSpeed, double maximumAcceleration, double maximumJerk) { if (speed >= maximumSpeed - BoundaryTolerance) { if (acceleration > 0d) return -maximumJerk; return acceleration < 0d ? maximumJerk : 0d; } double speedToReduceAccelerationToZero = acceleration > 0d ? acceleration * acceleration / (2d * maximumJerk) : 0d; if (speed + speedToReduceAccelerationToZero >= maximumSpeed - BoundaryTolerance) return acceleration > 0d ? -maximumJerk : 0d; return acceleration < maximumAcceleration - BoundaryTolerance ? maximumJerk : 0d; } private static EmTerminalType ToTerminalType(EmBoundaryType boundaryType) { return boundaryType == EmBoundaryType.Goal ? EmTerminalType.Goal : boundaryType == EmBoundaryType.GearSwitchApproach || boundaryType == EmBoundaryType.GearSwitchDeparture ? EmTerminalType.GearSwitch : EmTerminalType.RollingSafetyStop; } private static bool IsPositiveFinite(double value) { return IsFinite(value) && value > 0d; } private static bool IsFinite(double value) { return !double.IsNaN(value) && !double.IsInfinity(value); } }