Files
ParkingRobot/ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/TrajectorySampleSchedule.cs
T

216 lines
9.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
internal sealed class TrajectorySampleSchedule
{
private const double ZeroTolerance = 1e-12d;
public TrajectorySampleSchedule(LongitudinalCandidate candidate, double outputTimeStepSeconds, double holdDurationSeconds,
EmLongitudinalMode mode, bool resampleMotion)
{
if (candidate == null)
throw new ArgumentNullException(nameof(candidate));
if (!IsFinite(outputTimeStepSeconds) || outputTimeStepSeconds <= 0d)
throw new ArgumentOutOfRangeException(nameof(outputTimeStepSeconds));
if (!IsFinite(holdDurationSeconds) || holdDurationSeconds < 0d)
throw new ArgumentOutOfRangeException(nameof(holdDurationSeconds));
if (!Enum.IsDefined(typeof(EmLongitudinalMode), mode))
throw new ArgumentOutOfRangeException(nameof(mode));
var samples = new List<TrajectorySample>(candidate.KnotTimes.Count + 4);
double previousPathS = double.NegativeInfinity;
if (resampleMotion)
{
double finalTime = candidate.KnotTimes[candidate.KnotTimes.Count - 1];
int sourceInterval = 0;
for (double sampleTime = 0d; sampleTime < finalTime - ZeroTolerance;
sampleTime += outputTimeStepSeconds)
{
AddSample(Interpolate(candidate, sampleTime, ref sourceInterval), samples, ref previousPathS, candidate);
}
AddSample(Interpolate(candidate, finalTime, ref sourceInterval), samples, ref previousPathS, candidate);
}
else
{
for (int index = 0; index < candidate.KnotTimes.Count; index++)
{
AddSample(new TrajectorySample(candidate.KnotTimes[index], candidate.S[index],
Math.Max(0d, candidate.U[index]), candidate.A[index], index < candidate.J.Count ? candidate.J[index] : 0d,
false), samples, ref previousPathS, candidate);
}
}
if (mode != EmLongitudinalMode.ExactStopAtBoundary)
{
Samples = new ReadOnlyCollection<TrajectorySample>(samples);
TerminalAnchorSampleIndex = -1;
return;
}
int sourceStabilizationStart = resampleMotion
? FindTerminalStationaryTailStart(candidate)
: LongitudinalTerminalSchedule.GetStabilizationStartIndex(candidate.KnotTimes, outputTimeStepSeconds);
double stabilizationStartTime = candidate.KnotTimes[sourceStabilizationStart];
int stabilizationStart = 0;
while (stabilizationStart < samples.Count - 1 &&
samples[stabilizationStart].TimeFromStart < stabilizationStartTime - ZeroTolerance)
{
stabilizationStart++;
}
double stopPathS = samples[stabilizationStart].PathS;
for (int index = stabilizationStart; index < samples.Count; index++)
{
if (Math.Abs(samples[index].PathS - stopPathS) > ZeroTolerance ||
Math.Abs(samples[index].ProgressSpeed) > ZeroTolerance || Math.Abs(samples[index].Acceleration) > ZeroTolerance ||
Math.Abs(samples[index].Jerk) > ZeroTolerance)
{
throw new ArgumentException("An exact stop requires a stationary S/U/A/J tail.", nameof(candidate));
}
}
TerminalAnchorSampleIndex = stabilizationStart;
TrajectorySample terminal = samples[samples.Count - 1];
double holdElapsed = 0d;
while (holdElapsed < holdDurationSeconds - ZeroTolerance)
{
holdElapsed = Math.Min(holdDurationSeconds, holdElapsed + outputTimeStepSeconds);
samples.Add(new TrajectorySample(terminal.TimeFromStart + holdElapsed, terminal.PathS, 0d, 0d, 0d, true));
}
Samples = new ReadOnlyCollection<TrajectorySample>(samples);
}
public IReadOnlyList<TrajectorySample> Samples { get; }
public int TerminalAnchorSampleIndex { get; }
internal static bool ExceedsMaximumSampleCount(LongitudinalCandidate candidate, double outputTimeStepSeconds,
double holdDurationSeconds, EmLongitudinalMode mode, bool resampleMotion, int maximumSampleCount,
out int requiredSampleCount)
{
requiredSampleCount = 0;
if (candidate == null)
throw new ArgumentNullException(nameof(candidate));
if (maximumSampleCount < 1)
throw new ArgumentOutOfRangeException(nameof(maximumSampleCount));
if (resampleMotion)
{
double finalTime = candidate.KnotTimes[candidate.KnotTimes.Count - 1];
for (double sampleTime = 0d; sampleTime < finalTime - ZeroTolerance;
sampleTime += outputTimeStepSeconds)
{
if (IncrementAndExceedsMaximum(ref requiredSampleCount, maximumSampleCount))
return true;
}
if (IncrementAndExceedsMaximum(ref requiredSampleCount, maximumSampleCount))
return true;
}
else
{
for (int index = 0; index < candidate.KnotTimes.Count; index++)
{
if (IncrementAndExceedsMaximum(ref requiredSampleCount, maximumSampleCount))
return true;
}
}
if (mode != EmLongitudinalMode.ExactStopAtBoundary)
return false;
double holdElapsed = 0d;
while (holdElapsed < holdDurationSeconds - ZeroTolerance)
{
holdElapsed = Math.Min(holdDurationSeconds, holdElapsed + outputTimeStepSeconds);
if (IncrementAndExceedsMaximum(ref requiredSampleCount, maximumSampleCount))
return true;
}
return false;
}
private static void AddSample(TrajectorySample sample, ICollection<TrajectorySample> samples,
ref double previousPathS, LongitudinalCandidate candidate)
{
if (sample.PathS < previousPathS)
throw new ArgumentException("Trajectory PathS cannot decrease.", nameof(candidate));
if (sample.ProgressSpeed < -ZeroTolerance)
throw new ArgumentException("Longitudinal progress speed cannot be negative.", nameof(candidate));
samples.Add(sample);
previousPathS = sample.PathS;
}
private static bool IncrementAndExceedsMaximum(ref int sampleCount, int maximumSampleCount)
{
checked { sampleCount++; }
return sampleCount > maximumSampleCount;
}
private static TrajectorySample Interpolate(LongitudinalCandidate candidate, double sampleTime, ref int sourceInterval)
{
int lastKnot = candidate.KnotTimes.Count - 1;
if (sampleTime >= candidate.KnotTimes[lastKnot] - ZeroTolerance)
{
return new TrajectorySample(candidate.KnotTimes[lastKnot], candidate.S[lastKnot],
Math.Max(0d, candidate.U[lastKnot]), candidate.A[lastKnot], 0d, false);
}
while (sourceInterval < lastKnot - 1 &&
sampleTime >= candidate.KnotTimes[sourceInterval + 1] - ZeroTolerance)
{
sourceInterval++;
}
if (Math.Abs(sampleTime - candidate.KnotTimes[sourceInterval]) <= ZeroTolerance)
{
return new TrajectorySample(candidate.KnotTimes[sourceInterval], candidate.S[sourceInterval],
Math.Max(0d, candidate.U[sourceInterval]), candidate.A[sourceInterval], candidate.J[sourceInterval], false);
}
double dt = sampleTime - candidate.KnotTimes[sourceInterval];
double jerk = candidate.J[sourceInterval];
double acceleration = candidate.A[sourceInterval] + jerk * dt;
double speed = candidate.U[sourceInterval] + candidate.A[sourceInterval] * dt + 0.5d * jerk * dt * dt;
double pathS = candidate.S[sourceInterval] + candidate.U[sourceInterval] * dt +
0.5d * candidate.A[sourceInterval] * dt * dt + jerk * dt * dt * dt / 6d;
return new TrajectorySample(sampleTime, pathS, Math.Max(0d, speed), acceleration, jerk, false);
}
private static int FindTerminalStationaryTailStart(LongitudinalCandidate candidate)
{
int start = candidate.KnotTimes.Count - 1;
double terminalPathS = candidate.S[start];
while (start > 0 && Math.Abs(candidate.S[start - 1] - terminalPathS) <= ZeroTolerance &&
Math.Abs(candidate.U[start - 1]) <= ZeroTolerance && Math.Abs(candidate.A[start - 1]) <= ZeroTolerance &&
Math.Abs(candidate.J[start - 1]) <= ZeroTolerance)
{
start--;
}
return start;
}
private static bool IsFinite(double value)
{
return !double.IsNaN(value) && !double.IsInfinity(value);
}
}
internal sealed class TrajectorySample
{
public TrajectorySample(double timeFromStart, double pathS, double progressSpeed, double acceleration, double jerk,
bool isHoldSample)
{
TimeFromStart = timeFromStart;
PathS = pathS;
ProgressSpeed = progressSpeed;
Acceleration = acceleration;
Jerk = jerk;
IsHoldSample = isHoldSample;
}
public double TimeFromStart { get; }
public double PathS { get; }
public double ProgressSpeed { get; }
public double Acceleration { get; }
public double Jerk { get; }
public bool IsHoldSample { get; }
}