using System; using System.Collections.Generic; using System.Collections.ObjectModel; namespace MultiWheelC.TrajectoryPlanning.EMPlanner; /// Immutable time-knot s/u/a/j iterate used for ST linearization and strict validation. public sealed class LongitudinalCandidate { public LongitudinalCandidate(IReadOnlyList knotTimes, IReadOnlyList s, IReadOnlyList u, IReadOnlyList a, IReadOnlyList j) { KnotTimes = CopyTimes(knotTimes); S = CopyValues(s, KnotTimes.Count, nameof(s)); U = CopyValues(u, KnotTimes.Count, nameof(u)); A = CopyValues(a, KnotTimes.Count, nameof(a)); J = CopyValues(j, KnotTimes.Count - 1, nameof(j)); } public IReadOnlyList KnotTimes { get; } public IReadOnlyList S { get; } public IReadOnlyList U { get; } public IReadOnlyList A { get; } public IReadOnlyList J { get; } public static LongitudinalCandidate Integrate(IReadOnlyList knotTimes, double initialS, double initialU, double initialA, IReadOnlyList jerk) { IReadOnlyList times = CopyTimes(knotTimes); if (!IsFinite(initialS) || !IsFinite(initialU) || !IsFinite(initialA)) throw new ArgumentOutOfRangeException(nameof(initialS)); IReadOnlyList copiedJerk = CopyValues(jerk, times.Count - 1, nameof(jerk)); var s = new double[times.Count]; var u = new double[times.Count]; var a = new double[times.Count]; s[0] = initialS; u[0] = initialU; a[0] = initialA; for (int index = 0; index < copiedJerk.Count; index++) { double dt = times[index + 1] - times[index]; double currentJerk = copiedJerk[index]; a[index + 1] = a[index] + dt * currentJerk; u[index + 1] = u[index] + dt * a[index] + 0.5d * dt * dt * currentJerk; s[index + 1] = s[index] + dt * u[index] + 0.5d * dt * dt * a[index] + dt * dt * dt * currentJerk / 6d; } return new LongitudinalCandidate(times, s, u, a, copiedJerk); } public bool SatisfiesExactDiscreteDynamics(double tolerance) { if (!IsFinite(tolerance) || tolerance < 0d) throw new ArgumentOutOfRangeException(nameof(tolerance)); for (int index = 0; index < J.Count; index++) { double dt = KnotTimes[index + 1] - KnotTimes[index]; if (Math.Abs(A[index + 1] - (A[index] + dt * J[index])) > tolerance || Math.Abs(U[index + 1] - (U[index] + dt * A[index] + 0.5d * dt * dt * J[index])) > tolerance || Math.Abs(S[index + 1] - (S[index] + dt * U[index] + 0.5d * dt * dt * A[index] + dt * dt * dt * J[index] / 6d)) > tolerance) { return false; } } return true; } public static IReadOnlyList CreateKnotTimes(double timeHorizonSeconds, double outputTimeStepSeconds) { if (!IsFinite(timeHorizonSeconds) || !IsFinite(outputTimeStepSeconds) || timeHorizonSeconds <= 0d || outputTimeStepSeconds <= 0d) { throw new ArgumentOutOfRangeException(nameof(timeHorizonSeconds)); } int intervalCount = checked((int)Math.Ceiling(timeHorizonSeconds / outputTimeStepSeconds)); var times = new double[intervalCount + 1]; for (int index = 0; index < intervalCount; index++) times[index] = index * outputTimeStepSeconds; times[intervalCount] = timeHorizonSeconds; return new ReadOnlyCollection(times); } private static IReadOnlyList CopyTimes(IReadOnlyList source) { if (source == null || source.Count < 2) throw new ArgumentException("At least two strictly increasing time knots are required.", nameof(source)); var copy = new List(source.Count); double previous = double.NegativeInfinity; for (int index = 0; index < source.Count; index++) { if (!IsFinite(source[index]) || source[index] <= previous) throw new ArgumentException("Time knots must be finite and strictly increasing.", nameof(source)); if (index == 0 && Math.Abs(source[index]) > 1e-12d) throw new ArgumentException("The first time knot must be exact zero.", nameof(source)); copy.Add(source[index]); previous = source[index]; } return new ReadOnlyCollection(copy); } private static IReadOnlyList CopyValues(IReadOnlyList source, int expectedCount, string parameterName) { if (source == null || source.Count != expectedCount) throw new ArgumentException("Longitudinal values do not match the time-knot layout.", parameterName); var copy = new List(source.Count); for (int index = 0; index < source.Count; index++) { if (!IsFinite(source[index])) throw new ArgumentOutOfRangeException(parameterName); copy.Add(source[index]); } return new ReadOnlyCollection(copy); } private static bool IsFinite(double value) { return !double.IsNaN(value) && !double.IsInfinity(value); } }