using System; using System.Collections.Generic; using MultiWheelC.TrajectoryPlanning.CoarsePath; namespace MultiWheelC.TrajectoryPlanning.EMPlanner; /// /// 在纵横向优化前选定的参考距离终端,保证不会跨越当前方向段。 /// 所有参考站量以当前段局部 S(m)表示;边界类型和纵向模式明确滚动延续、接近停车或精确停车语义。 /// public sealed class PlanningHorizonSelection { /// /// 创建仅供本程序集发布的窗口与停车边界选择。 /// 参数:两个参考站均为局部 S(m),boundary/terminal/mode 为已判定枚举;hasStopBoundary 表示终端是否受真实停车边界约束。 /// internal PlanningHorizonSelection(double windowEndReferenceS, EmBoundaryType windowEndBoundaryType, EmTerminalType terminalType, EmLongitudinalMode longitudinalMode, double stopBoundaryReferenceS, bool hasStopBoundary) { WindowEndReferenceS = windowEndReferenceS; WindowEndBoundaryType = windowEndBoundaryType; TerminalType = terminalType; LongitudinalMode = longitudinalMode; StopBoundaryReferenceS = stopBoundaryReferenceS; HasStopBoundary = hasStopBoundary; } /// /// 本次优化窗口末端的局部参考弧长 S,单位 m。 /// public double WindowEndReferenceS { get; } /// /// 规划输出的终端参考弧长 S,当前与窗口末端相同,单位 m。 /// public double TerminalReferenceS => WindowEndReferenceS; /// /// 窗口末端的边界语义;中途截断时为滚动安全停车边界。 /// public EmBoundaryType WindowEndBoundaryType { get; } /// /// 供终端约束使用的目标、换向或滚动终端类型。 /// public EmTerminalType TerminalType { get; } /// /// 纵向候选应滚动延续、接近停车边界还是在边界精确停车的模式。 /// public EmLongitudinalMode LongitudinalMode { get; } /// /// 实际目标或换向停车边界的局部参考弧长 S,单位 m;无此边界时仍保存段末。 /// public double StopBoundaryReferenceS { get; } /// /// 是否存在必须在当前方向段内处理的目标或换向停车边界。 /// public bool HasStopBoundary { get; } } /// /// 根据段边界、初始纵向状态和调度窗口选择 EM 优化地平线。 /// 输入速度为 m/s、加速度为 m/s²、距离为 m;无效输入、越界初态或不足以停车的段会以明确状态拒绝。 /// public sealed class PlanningHorizonSelector { /// /// 段终点、初态限值和停车距离比较使用的局部 S/距离容差,单位 m。 /// private const double BoundaryTolerance = 1e-8d; /// /// 为当前方向段选择不跨界的参考距离窗口和纵向终端模式。 /// 参数:currentSegmentReferenceS 为 m,初速为 m/s,初加速度为 m/s²,configuration 提供 m、s 制限值;planningScope 必须为定义的范围枚举。 /// 返回:成功时给出 selection;配置/初态非法返回 InvalidInput,停车距离越段返回 StoppingDistanceInsufficient,非法全段终边界返回 InvalidReferencePath。 /// public EmPlanningStatus Select(DirectionSegmentView segment, double currentSegmentReferenceS, double initialProgressSpeedMetersPerSecond, double initialAccelerationMetersPerSecondSquared, EmPlanningScope planningScope, 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) || !Enum.IsDefined(typeof(EmPlanningScope), planningScope)) { 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; } if (planningScope == EmPlanningScope.FullDirectionSegment) { EmBoundaryType boundary = segment.EndBoundary.BoundaryType; if (!IsStopBoundary(boundary)) { failureReason = "A full direction segment must end at Goal or GearSwitch."; return EmPlanningStatus.InvalidReferencePath; } selection = new PlanningHorizonSelection(segment.LengthMeters, boundary, ToTerminalType(boundary), EmLongitudinalMode.ExactStopAtBoundary, segment.LengthMeters, true); return EmPlanningStatus.Success; } double windowEnd = Math.Min(currentSegmentReferenceS + scheduling.DistanceHorizonMeters, segment.LengthMeters); bool windowReachesSegmentEnd = windowEnd >= segment.LengthMeters - BoundaryTolerance; bool hasStopBoundary = windowReachesSegmentEnd && IsStopBoundary(segment.EndBoundary.BoundaryType); EmBoundaryType windowEndBoundaryType = windowReachesSegmentEnd ? segment.EndBoundary.BoundaryType : EmBoundaryType.RollingSafetyStop; if (!hasStopBoundary) { selection = new PlanningHorizonSelection(windowEnd, windowEndBoundaryType, EmTerminalType.RollingSafetyStop, EmLongitudinalMode.RollingContinuation, segment.LengthMeters, false); return EmPlanningStatus.Success; } IReadOnlyList knotTimes = LongitudinalCandidate.CreateKnotTimes( scheduling.TimeHorizonSeconds, scheduling.OutputTimeStepSeconds); int stabilizationStart = LongitudinalTerminalSchedule.GetStabilizationStartIndex( knotTimes, scheduling.OutputTimeStepSeconds); double availableMotionTime = knotTimes[stabilizationStart]; double maximumStoppedDistance = JerkLimitedStoppingMath.CalculateMaximumStoppedDistance( initialProgressSpeedMetersPerSecond, initialAccelerationMetersPerSecondSquared, directionMaximum, longitudinal.MaximumAccelerationMetersPerSecondSquared, longitudinal.MaximumDecelerationMetersPerSecondSquared, longitudinal.MaximumJerkMetersPerSecondCubed, availableMotionTime); EmLongitudinalMode mode = remainingSegment <= maximumStoppedDistance + BoundaryTolerance ? EmLongitudinalMode.ExactStopAtBoundary : EmLongitudinalMode.ApproachStopBoundary; selection = new PlanningHorizonSelection(segment.LengthMeters, windowEndBoundaryType, ToTerminalType(segment.EndBoundary.BoundaryType), mode, segment.LengthMeters, true); return EmPlanningStatus.Success; } /// /// 判断边界是否要求车辆在当前段内停止。 /// 参数:boundaryType 为边界枚举;返回:Goal 或 GearSwitchApproach 时为 true,其余边界允许滚动延续。 /// private static bool IsStopBoundary(EmBoundaryType boundaryType) { return boundaryType == EmBoundaryType.Goal || boundaryType == EmBoundaryType.GearSwitchApproach; } /// /// 将参考边界语义映射为纵向终端类型。 /// 参数:boundaryType 为边界枚举;返回:目标、任一换向边界分别映射到 Goal、GearSwitch,其余映射为 RollingSafetyStop。 /// private static EmTerminalType ToTerminalType(EmBoundaryType boundaryType) { return boundaryType == EmBoundaryType.Goal ? EmTerminalType.Goal : boundaryType == EmBoundaryType.GearSwitchApproach || boundaryType == EmBoundaryType.GearSwitchDeparture ? EmTerminalType.GearSwitch : EmTerminalType.RollingSafetyStop; } /// /// 判断配置中的正量是否可用于时间、距离或动力学计算。 /// 参数:value 为对应单位的标量;返回:仅有限且严格大于零时为 true。 /// private static bool IsPositiveFinite(double value) { return IsFinite(value) && value > 0d; } /// /// 判断规划输入是否为有限实数。 /// 参数:value 为任意标量;返回:NaN 与无穷均返回 false。 /// private static bool IsFinite(double value) { return !double.IsNaN(value) && !double.IsInfinity(value); } }