feat: build EM path speed limits
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using EMPlannerVerificationHost;
|
||||
using MultiWheelC.TrajectoryPlanning.CoarsePath;
|
||||
using MultiWheelC.TrajectoryPlanning.PathSmoothing;
|
||||
|
||||
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
|
||||
|
||||
internal static class LongitudinalModelChecks
|
||||
{
|
||||
public static void Run()
|
||||
{
|
||||
VerifiesFinitePathSIndexedSpeedEnvelope();
|
||||
VerifiesStoppingPrecheckBeforeQpAssembly();
|
||||
VerifiesReferenceHorizonSelectionKeepsTheCurrentSegmentBoundary();
|
||||
}
|
||||
|
||||
private static void VerifiesFinitePathSIndexedSpeedEnvelope()
|
||||
{
|
||||
LateralPath directionPath = CreatePath(new[]
|
||||
{
|
||||
new PathFixture(0d, 0d, 0d, 0d),
|
||||
new PathFixture(1d, 1d, 0d, 0d),
|
||||
});
|
||||
var directionInput = new LongitudinalPlanningInput(directionPath, TravelDirection.Forward, 0d, 0d,
|
||||
EmTerminalType.Goal, EmPlannerConfiguration.CreateDefault(), Array.Empty<double>(), Array.Empty<double>());
|
||||
EmPlanningStatus directionStatus = new PathSpeedLimitBuilder().Build(directionInput,
|
||||
out PathSpeedLimit directionEnvelope, out string directionFailureReason);
|
||||
Verification.Equal(EmPlanningStatus.Success, directionStatus, "default direction speed limit status: " +
|
||||
directionFailureReason);
|
||||
Verification.NearlyEqual(0.20d, directionEnvelope.DirectionMaximumSpeedMetersPerSecond,
|
||||
"default direction speed limit");
|
||||
|
||||
EmPlannerConfiguration configuration = EmPlannerConfiguration.CreateDefault();
|
||||
configuration.Longitudinal.MaximumForwardSpeedMetersPerSecond = 1d;
|
||||
configuration.Longitudinal.MaximumReverseSpeedMetersPerSecond = 1d;
|
||||
LateralPath path = CreatePath(new[]
|
||||
{
|
||||
new PathFixture(10d, 0d, 0d, 0d),
|
||||
new PathFixture(20d, 2d, 2d, 4d),
|
||||
new PathFixture(20.5d, 4d, 20d, 0d),
|
||||
new PathFixture(21d, 5d, 0d, 0d),
|
||||
});
|
||||
var input = new LongitudinalPlanningInput(path, TravelDirection.Forward, 0d, 0d,
|
||||
EmTerminalType.Goal, configuration, Array.Empty<double>(), Array.Empty<double>());
|
||||
|
||||
EmPlanningStatus status = new PathSpeedLimitBuilder().Build(input, out PathSpeedLimit envelope,
|
||||
out string failureReason);
|
||||
Verification.Equal(EmPlanningStatus.Success, status, "speed envelope status: " + failureReason);
|
||||
Verification.NearlyEqual(1d, envelope.DirectionMaximumSpeedMetersPerSecond, "overridden direction speed limit");
|
||||
Verification.NearlyEqual(Math.Sqrt(0.20d / 2d), envelope.LateralAccelerationLimitAt(2d),
|
||||
"curvature lateral-acceleration limit");
|
||||
Verification.NearlyEqual(0.50d / 4d, envelope.CurvatureRateLimitAt(2d), "curvature-rate limit");
|
||||
Verification.True(double.IsFinite(envelope.LateralAccelerationLimitAt(0d)) &&
|
||||
double.IsFinite(envelope.CurvatureRateLimitAt(0d)), "zero curvature limits stay finite");
|
||||
Verification.NearlyEqual(Math.Sqrt(2d * 0.30d * (5d - 4d)), envelope.StoppingLimitAt(4d),
|
||||
"stopping speed limit");
|
||||
Verification.NearlyEqual(Math.Sqrt(0.20d / 20d), envelope.MaximumSpeedAt(4d),
|
||||
"combined limit chooses the finite minimum");
|
||||
Verification.NearlyEqual((envelope.MaximumSpeedAt(0d) + envelope.MaximumSpeedAt(2d)) / 2d,
|
||||
envelope.MaximumSpeedAt(1d), "speed envelope interpolates by PathS rather than ReferenceS");
|
||||
Verification.NearlyEqual(0d, envelope.MaximumSpeedAt(5d), "terminal speed is exactly zero");
|
||||
}
|
||||
|
||||
private static void VerifiesStoppingPrecheckBeforeQpAssembly()
|
||||
{
|
||||
EmPlannerConfiguration configuration = EmPlannerConfiguration.CreateDefault();
|
||||
LateralPath shortPath = CreatePath(new[]
|
||||
{
|
||||
new PathFixture(0d, 0d, 0d, 0d),
|
||||
new PathFixture(100d, 0.01d, 0d, 0d),
|
||||
});
|
||||
var input = new LongitudinalPlanningInput(shortPath, TravelDirection.Forward, 0.20d, 0.20d,
|
||||
EmTerminalType.RollingSafetyStop, configuration, Array.Empty<double>(), Array.Empty<double>());
|
||||
|
||||
EmPlanningStatus status = new PathSpeedLimitBuilder().Build(input, out PathSpeedLimit envelope,
|
||||
out string failureReason);
|
||||
Verification.Equal(EmPlanningStatus.StoppingDistanceInsufficient, status,
|
||||
"jerk/deceleration stopping precheck status");
|
||||
Verification.True(envelope == null, "stopping-distance failure does not create a speed envelope");
|
||||
Verification.True(failureReason.Length != 0, "stopping-distance failure explains the rejection");
|
||||
}
|
||||
|
||||
private static void VerifiesReferenceHorizonSelectionKeepsTheCurrentSegmentBoundary()
|
||||
{
|
||||
EmPlannerConfiguration configuration = EmPlannerConfiguration.CreateDefault();
|
||||
DirectionSegmentView shortGoal = CreateSegment(0.25d, EmBoundaryType.Goal);
|
||||
EmPlanningStatus status = new PlanningHorizonSelector().Select(shortGoal, 0d, 0d, 0d, configuration,
|
||||
out PlanningHorizonSelection goalSelection, out string goalFailure);
|
||||
|
||||
Verification.Equal(EmPlanningStatus.Success, status, "goal horizon status: " + goalFailure);
|
||||
Verification.Equal(EmTerminalType.Goal, goalSelection.TerminalType, "goal terminal type");
|
||||
Verification.NearlyEqual(0.25d, goalSelection.TerminalReferenceS, "goal terminal reference S");
|
||||
|
||||
DirectionSegmentView longSegment = CreateSegment(4d, EmBoundaryType.GearSwitchApproach);
|
||||
status = new PlanningHorizonSelector().Select(longSegment, 0d, 0d, 0d, configuration,
|
||||
out PlanningHorizonSelection rollingSelection, out string rollingFailure);
|
||||
Verification.Equal(EmPlanningStatus.Success, status, "rolling horizon status: " + rollingFailure);
|
||||
Verification.Equal(EmTerminalType.RollingSafetyStop, rollingSelection.TerminalType, "rolling terminal type");
|
||||
Verification.True(rollingSelection.TerminalReferenceS >= 0d && rollingSelection.TerminalReferenceS < 4d,
|
||||
"rolling horizon remains within the current segment");
|
||||
}
|
||||
|
||||
private static LateralPath CreatePath(IReadOnlyList<PathFixture> fixtures)
|
||||
{
|
||||
var points = new List<LateralPathPoint>(fixtures.Count);
|
||||
for (int index = 0; index < fixtures.Count; index++)
|
||||
{
|
||||
PathFixture fixture = fixtures[index];
|
||||
points.Add(new LateralPathPoint(fixture.ReferenceS, fixture.PathS, 0d, 0d, 0d, 0d, fixture.PathS, 0d,
|
||||
0d, fixture.Curvature, fixture.Curvature, fixture.CurvatureDerivative));
|
||||
}
|
||||
return new LateralPath(points, true);
|
||||
}
|
||||
|
||||
private static DirectionSegmentView CreateSegment(double length, EmBoundaryType endBoundaryType)
|
||||
{
|
||||
var points = new List<SmoothedPathPoint>
|
||||
{
|
||||
Point(0d, 0d),
|
||||
Point(length, length),
|
||||
};
|
||||
return new DirectionSegmentView(0, TravelDirection.Forward, points,
|
||||
new ReferenceBoundary(0, 0d, EmBoundaryType.None, 0d),
|
||||
new ReferenceBoundary(0, length, endBoundaryType, length), 0d);
|
||||
}
|
||||
|
||||
private static SmoothedPathPoint Point(double x, double s)
|
||||
{
|
||||
return new SmoothedPathPoint(x, 0d, 0d, 0d, s, TravelDirection.Forward, 0d, 0d, 0d, 1d, false,
|
||||
SmoothedPathPointSource.Anchor);
|
||||
}
|
||||
|
||||
private sealed class PathFixture
|
||||
{
|
||||
public PathFixture(double referenceS, double pathS, double curvature, double curvatureDerivative)
|
||||
{
|
||||
ReferenceS = referenceS;
|
||||
PathS = pathS;
|
||||
Curvature = curvature;
|
||||
CurvatureDerivative = curvatureDerivative;
|
||||
}
|
||||
|
||||
public double ReferenceS { get; }
|
||||
public double PathS { get; }
|
||||
public double Curvature { get; }
|
||||
public double CurvatureDerivative { get; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user