using System;
using MultiWheelC.Control.Abstractions;
using MultiWheelC.Control.Lateral;
using MultiWheelC.Trajectory;
using MyParking.Shared;
namespace MultiWheelC.Tests
{
///
/// 验证Stanley控制器在前进和倒车时的横向误差符号及差动转角方向。
///
internal static class Program
{
private const double SpeedMagnitudeMetersPerSecond = 0.4;
private const double TestLateralErrorMeters = 0.1;
private const double TestHeadingErrorRadians = 0.1;
private const double TestCurvaturePerMeter = 0.2;
///
/// 运行不依赖宿主、Detour或实车底盘的控制器数学测试。
///
private static void Main()
{
foreach (var travelDirection in new[] { 1.0, -1.0 })
{
VerifyCrossTrackConvergence(
travelDirection,
TestLateralErrorMeters);
VerifyCrossTrackConvergence(
travelDirection,
-TestLateralErrorMeters);
VerifyHeadingDirection(travelDirection);
VerifyCurvatureDirection(travelDirection);
}
Console.WriteLine(
"Stanley前进/倒车横向符号测试通过。共8个场景。");
FleetKinematicsTests.Run();
FleetControllerTests.Run();
}
///
/// 验证共同转角产生的横向速度始终使轨迹点序横向误差绝对值减小。
///
private static void VerifyCrossTrackConvergence(
double travelDirection,
double lateralErrorMeters)
{
var signedSpeedMetersPerSecond =
travelDirection *
SpeedMagnitudeMetersPerSecond;
var controller = CreateController();
var command = controller.Compute(
CreateContext(
signedSpeedMetersPerSecond,
lateralErrorMeters,
headingErrorRadians: 0.0,
feedforwardCurvaturePerMeter: 0.0));
var bodyLateralSpeedMetersPerSecond =
signedSpeedMetersPerSecond *
Math.Sin(command.CommonAngleRadians);
// 轨迹执行方向在倒车时与车体X轴相反,因此需要先把车体
// 横向速度换算到轨迹点序坐标系,再计算参考轨迹相对车辆的误差变化率。
var lateralErrorDerivativeMetersPerSecond =
-travelDirection *
bodyLateralSpeedMetersPerSecond;
AssertTrue(
lateralErrorMeters *
lateralErrorDerivativeMetersPerSecond < 0.0,
$"横向误差没有收敛:direction={travelDirection}," +
$"error={lateralErrorMeters:F3}m," +
$"common={command.CommonAngleRadians:F6}rad," +
$"errorDerivative={lateralErrorDerivativeMetersPerSecond:F6}m/s。");
}
///
/// 验证航向误差差动转角在倒车时仍按行驶方向反号。
///
private static void VerifyHeadingDirection(
double travelDirection)
{
var command = CreateController().Compute(
CreateContext(
travelDirection *
SpeedMagnitudeMetersPerSecond,
lateralErrorMeters: 0.0,
headingErrorRadians:
TestHeadingErrorRadians,
feedforwardCurvaturePerMeter: 0.0));
AssertSameSign(
command.DifferentialAngleRadians,
travelDirection,
"航向误差差动转角");
}
///
/// 验证正曲率前馈差动转角在倒车时仍按行驶方向反号。
///
private static void VerifyCurvatureDirection(
double travelDirection)
{
var command = CreateController().Compute(
CreateContext(
travelDirection *
SpeedMagnitudeMetersPerSecond,
lateralErrorMeters: 0.0,
headingErrorRadians: 0.0,
feedforwardCurvaturePerMeter:
TestCurvaturePerMeter));
AssertSameSign(
command.DifferentialAngleRadians,
travelDirection,
"曲率前馈差动转角");
}
///
/// 创建使用固定参数的无状态Stanley控制器。
///
private static StanleyLateralController CreateController()
{
return new StanleyLateralController(
controlPointRadiusMeters: 0.5,
crossTrackGainPerSecond: 1.0,
headingErrorGain: 1.0,
minimumSpeedMetersPerSecond: 0.05,
useActualSpeedForGain: true);
}
///
/// 创建只包含本次符号测试所需字段的轨迹跟踪上下文。
///
private static PathTrackingContext CreateContext(
double signedSpeedMetersPerSecond,
double lateralErrorMeters,
double headingErrorRadians,
double feedforwardCurvaturePerMeter)
{
var actualTwistInBody = new Twist2D(
signedSpeedMetersPerSecond,
0.0,
0.0);
var referencePoint = new TrajectoryPoint(
arcLengthMeters: 0.0,
poseInWorld: Pose2D.Identity,
curvaturePerMeter: 0.0,
referenceSpeedMetersPerSecond:
signedSpeedMetersPerSecond);
var projection = new TrajectoryProjection(
segmentStartIndex: 0,
referencePoint: referencePoint,
lateralErrorMeters: lateralErrorMeters,
headingErrorRadians: headingErrorRadians,
distanceToTrajectoryMeters:
Math.Abs(lateralErrorMeters),
remainingDistanceMeters: 1.0);
return new PathTrackingContext(
actualTwistInBody,
true,
projection,
signedSpeedMetersPerSecond,
feedforwardCurvaturePerMeter,
deltaTimeSeconds: 0.02);
}
///
/// 验证实际值与预期符号一致。
///
private static void AssertSameSign(
double actualValue,
double expectedSign,
string valueName)
{
AssertTrue(
Math.Sign(actualValue) ==
Math.Sign(expectedSign),
$"{valueName}方向错误:actual={actualValue:F6}," +
$"expectedSign={expectedSign:F0}。");
}
///
/// 条件不成立时抛出异常,使测试进程以非零状态结束。
///
private static void AssertTrue(
bool condition,
string failureMessage)
{
if (!condition)
{
throw new InvalidOperationException(
failureMessage);
}
}
}
}