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);
}
}