Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wpimath] Optimize 2nd derivative of quintic splines #3292

Merged
merged 9 commits into from
Dec 1, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public class CubicHermiteSpline extends Spline {
private static SimpleMatrix hermiteBasis;
private final SimpleMatrix m_coefficients;

private final ControlVector m_initialControlVector;
private final ControlVector m_finalControlVector;

/**
* Constructs a cubic hermite spline with the specified control vectors. Each control vector
* contains info about the location of the point and its first derivative.
Expand Down Expand Up @@ -60,6 +63,10 @@ public CubicHermiteSpline(
m_coefficients.set(4, i, m_coefficients.get(2, i) * (2 - i));
m_coefficients.set(5, i, m_coefficients.get(3, i) * (2 - i));
}

// Assign member variables.
m_initialControlVector = new ControlVector(xInitialControlVector, yInitialControlVector);
m_finalControlVector = new ControlVector(xFinalControlVector, yFinalControlVector);
}

/**
Expand All @@ -68,10 +75,30 @@ public CubicHermiteSpline(
* @return The coefficients matrix.
*/
@Override
protected SimpleMatrix getCoefficients() {
public SimpleMatrix getCoefficients() {
return m_coefficients;
}

/**
* Returns the initial control vector that created this spline.
*
* @return The initial control vector that created this spline.
*/
@Override
public ControlVector getInitialControlVector() {
return m_initialControlVector;
}

/**
* Returns the final control vector that created this spline.
*
* @return The final control vector that created this spline.
*/
@Override
public ControlVector getFinalControlVector() {
return m_finalControlVector;
}

/**
* Returns the hermite basis matrix for cubic hermite spline interpolation.
*
Expand Down Expand Up @@ -121,8 +148,8 @@ private SimpleMatrix makeHermiteBasis() {
* @return The control vector matrix for a dimension.
*/
private SimpleMatrix getControlVectorFromArrays(double[] initialVector, double[] finalVector) {
if (initialVector.length != 2 || finalVector.length != 2) {
throw new IllegalArgumentException("Size of vectors must be 2");
if (initialVector.length < 2 || finalVector.length < 2) {
throw new IllegalArgumentException("Size of vectors must be 2 or greater.");
}
return new SimpleMatrix(
4,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public class QuinticHermiteSpline extends Spline {
private static SimpleMatrix hermiteBasis;
private final SimpleMatrix m_coefficients;

private final ControlVector m_initialControlVector;
private final ControlVector m_finalControlVector;

/**
* Constructs a quintic hermite spline with the specified control vectors. Each control vector
* contains into about the location of the point, its first derivative, and its second derivative.
Expand Down Expand Up @@ -60,6 +63,10 @@ public QuinticHermiteSpline(
m_coefficients.set(4, i, m_coefficients.get(2, i) * (4 - i));
m_coefficients.set(5, i, m_coefficients.get(3, i) * (4 - i));
}

// Assign member variables.
m_initialControlVector = new ControlVector(xInitialControlVector, yInitialControlVector);
m_finalControlVector = new ControlVector(xFinalControlVector, yFinalControlVector);
}

/**
Expand All @@ -68,10 +75,30 @@ public QuinticHermiteSpline(
* @return The coefficients matrix.
*/
@Override
protected SimpleMatrix getCoefficients() {
public SimpleMatrix getCoefficients() {
return m_coefficients;
}

/**
* Returns the initial control vector that created this spline.
*
* @return The initial control vector that created this spline.
*/
@Override
public ControlVector getInitialControlVector() {
return m_initialControlVector;
}

/**
* Returns the final control vector that created this spline.
*
* @return The final control vector that created this spline.
*/
@Override
public ControlVector getFinalControlVector() {
return m_finalControlVector;
}

/**
* Returns the hermite basis matrix for quintic hermite spline interpolation.
*
Expand Down
16 changes: 15 additions & 1 deletion wpimath/src/main/java/edu/wpi/first/math/spline/Spline.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,21 @@ public abstract class Spline {
*
* @return The coefficients of the spline.
*/
protected abstract SimpleMatrix getCoefficients();
public abstract SimpleMatrix getCoefficients();

/**
* Returns the initial control vector that created this spline.
*
* @return The initial control vector that created this spline.
*/
public abstract ControlVector getInitialControlVector();

/**
* Returns the final control vector that created this spline.
*
* @return The final control vector that created this spline.
*/
public abstract ControlVector getFinalControlVector();

/**
* Gets the pose and curvature at some point t on the spline.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import edu.wpi.first.math.geometry.Translation2d;
import java.util.Arrays;
import java.util.List;
import org.ejml.simple.SimpleMatrix;

public final class SplineHelper {
/** Private constructor because this is a utility class. */
Expand Down Expand Up @@ -217,6 +218,79 @@ public static QuinticHermiteSpline[] getQuinticSplinesFromControlVectors(
return splines;
}

/**
* Optimizes the curvature of 2 or more quintic splines at knot points. Overall, this reduces the
* integral of the absolute value of the second derivative across the set of splines.
*
* @param splines An array of un-optimized quintic splines.
* @return An array of optimized quintic splines.
*/
@SuppressWarnings({"LocalVariableName", "PMD.AvoidInstantiatingObjectsInLoops"})
public static QuinticHermiteSpline[] optimizeCurvature(QuinticHermiteSpline[] splines) {
// If there's only spline in the array, we can't optimize anything so just return that.
if (splines.length < 2) {
return splines;
}

// Implements Section 4.1.2 of
// http://www2.informatik.uni-freiburg.de/~lau/students/Sprunk2008.pdf.

// Cubic splines minimize the integral of the second derivative's absolute value. Therefore, we
// can create cubic splines with the same 0th and 1st derivatives and the provided quintic
// splines, find the second derivative of those cubic splines and then use a weighted average
// for the second derivatives of the quintic splines.

QuinticHermiteSpline[] optimizedSplines = new QuinticHermiteSpline[splines.length];
for (int i = 0; i < splines.length - 1; ++i) {
QuinticHermiteSpline a = splines[i];
QuinticHermiteSpline b = splines[i + 1];

// Get the control vectors that created the quintic splines above.
Spline.ControlVector aInitial = a.getInitialControlVector();
Spline.ControlVector aFinal = a.getFinalControlVector();
Spline.ControlVector bInitial = b.getInitialControlVector();
Spline.ControlVector bFinal = b.getFinalControlVector();

// Create cubic splines with the same control vectors.
CubicHermiteSpline ca = new CubicHermiteSpline(aInitial.x, aFinal.x, aInitial.y, aFinal.y);
CubicHermiteSpline cb = new CubicHermiteSpline(bInitial.x, bFinal.x, bInitial.y, bFinal.y);

// Calculate the second derivatives at the knot points.
SimpleMatrix bases = new SimpleMatrix(4, 1, true, new double[] {1, 1, 1, 1});
SimpleMatrix combinedA = ca.getCoefficients().mult(bases);

double ddxA = combinedA.get(4, 0);
double ddyA = combinedA.get(5, 0);
double ddxB = cb.getCoefficients().get(4, 1);
double ddyB = cb.getCoefficients().get(5, 1);

// Calculate the parameters for the weighted average.
double dAB = Math.hypot(aFinal.x[0] - aInitial.x[0], aFinal.y[0] - aInitial.y[0]);
double dBC = Math.hypot(bFinal.x[0] - bInitial.x[0], bFinal.y[0] - bInitial.y[0]);
double alpha = dBC / (dAB + dBC);
double beta = dAB / (dAB + dBC);

// Calculate the weighted average.
double ddx = alpha * ddxA + beta * ddxB;
double ddy = alpha * ddyA + beta * ddyB;

// Create new splines.
optimizedSplines[i] =
new QuinticHermiteSpline(
aInitial.x,
new double[] {aFinal.x[0], aFinal.x[1], ddx},
aInitial.y,
new double[] {aFinal.y[0], aFinal.y[1], ddy});
optimizedSplines[i + 1] =
new QuinticHermiteSpline(
new double[] {bInitial.x[0], bInitial.x[1], ddx},
bFinal.x,
new double[] {bInitial.y[0], bInitial.y[1], ddy},
bFinal.y);
}
return optimizedSplines;
}

/**
* Thomas algorithm for solving tridiagonal systems Af = d.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,10 @@ public static Trajectory generateTrajectory(List<Pose2d> waypoints, TrajectoryCo
// Get the spline points
List<PoseWithCurvature> points;
try {
points = splinePointsFromSplines(SplineHelper.getQuinticSplinesFromWaypoints(newWaypoints));
points =
splinePointsFromSplines(
SplineHelper.optimizeCurvature(
SplineHelper.getQuinticSplinesFromWaypoints(newWaypoints)));
} catch (MalformedSplineException ex) {
reportError(ex.getMessage(), ex.getStackTrace());
return kDoNothingTrajectory;
Expand Down
4 changes: 3 additions & 1 deletion wpimath/src/main/native/cpp/spline/CubicHermiteSpline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ CubicHermiteSpline::CubicHermiteSpline(
wpi::array<double, 2> xInitialControlVector,
wpi::array<double, 2> xFinalControlVector,
wpi::array<double, 2> yInitialControlVector,
wpi::array<double, 2> yFinalControlVector) {
wpi::array<double, 2> yFinalControlVector)
: m_initialControlVector{xInitialControlVector, yInitialControlVector},
m_finalControlVector{xFinalControlVector, yFinalControlVector} {
const auto hermite = MakeHermiteBasis();
const auto x =
ControlVectorFromArrays(xInitialControlVector, xFinalControlVector);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ QuinticHermiteSpline::QuinticHermiteSpline(
wpi::array<double, 3> xInitialControlVector,
wpi::array<double, 3> xFinalControlVector,
wpi::array<double, 3> yInitialControlVector,
wpi::array<double, 3> yFinalControlVector) {
wpi::array<double, 3> yFinalControlVector)
: m_initialControlVector{xInitialControlVector, yInitialControlVector},
m_finalControlVector{xFinalControlVector, yFinalControlVector} {
const auto hermite = MakeHermiteBasis();
const auto x =
ControlVectorFromArrays(xInitialControlVector, xFinalControlVector);
Expand Down
75 changes: 75 additions & 0 deletions wpimath/src/main/native/cpp/spline/SplineHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,81 @@ std::vector<QuinticHermiteSpline> SplineHelper::QuinticSplinesFromWaypoints(
return splines;
}

std::vector<QuinticHermiteSpline> SplineHelper::OptimizeCurvature(
const std::vector<QuinticHermiteSpline>& splines) {
// If there's only one spline in the vector, we can't optimize anything so
// just return that.
if (splines.size() < 2) {
return splines;
}

// Implements Section 4.1.2 of
// http://www2.informatik.uni-freiburg.de/~lau/students/Sprunk2008.pdf.

// Cubic splines minimize the integral of the second derivative's absolute
// value. Therefore, we can create cubic splines with the same 0th and 1st
// derivatives and the provided quintic splines, find the second derivative of
// those cubic splines and then use a weighted average for the second
// derivatives of the quintic splines.

std::vector<QuinticHermiteSpline> optimizedSplines;
optimizedSplines.reserve(splines.size());
optimizedSplines.push_back(splines[0]);

for (size_t i = 0; i < splines.size() - 1; ++i) {
const auto& a = splines[i];
const auto& b = splines[i + 1];

// Get the control vectors that created the quintic splines above.
const auto& aInitial = a.GetInitialControlVector();
const auto& aFinal = a.GetFinalControlVector();
const auto& bInitial = b.GetInitialControlVector();
const auto& bFinal = b.GetFinalControlVector();

// Create cubic splines with the same control vectors.
auto Trim = [](const wpi::array<double, 3>& a) {
return wpi::array<double, 2>{a[0], a[1]};
};
CubicHermiteSpline ca{Trim(aInitial.x), Trim(aFinal.x), Trim(aInitial.y),
Trim(aFinal.y)};
CubicHermiteSpline cb{Trim(bInitial.x), Trim(bFinal.x), Trim(bInitial.y),
Trim(bFinal.y)};

// Calculate the second derivatives at the knot points.
frc::Vectord<4> bases{1.0, 1.0, 1.0, 1.0};
frc::Vectord<6> combinedA = ca.Coefficients() * bases;

double ddxA = combinedA(4);
double ddyA = combinedA(5);
double ddxB = cb.Coefficients()(4, 1);
double ddyB = cb.Coefficients()(5, 1);

// Calculate the parameters for weighted average.
double dAB =
std::hypot(aFinal.x[0] - aInitial.x[0], aFinal.y[0] - aInitial.y[0]);
double dBC =
std::hypot(bFinal.x[0] - bInitial.x[0], bFinal.y[0] - bInitial.y[0]);
double alpha = dBC / (dAB + dBC);
double beta = dAB / (dAB + dBC);

// Calculate the weighted average.
double ddx = alpha * ddxA + beta * ddxB;
double ddy = alpha * ddyA + beta * ddyB;

// Create new splines.
optimizedSplines[i] = {aInitial.x,
{aFinal.x[0], aFinal.x[1], ddx},
aInitial.y,
{aFinal.y[0], aFinal.y[1], ddy}};
optimizedSplines.push_back({{bInitial.x[0], bInitial.x[1], ddx},
bFinal.x,
{bInitial.y[0], bInitial.y[1], ddy},
bFinal.y});
}

return optimizedSplines;
}

void SplineHelper::ThomasAlgorithm(const std::vector<double>& a,
const std::vector<double>& b,
const std::vector<double>& c,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ Trajectory TrajectoryGenerator::GenerateTrajectory(

std::vector<SplineParameterizer::PoseWithCurvature> points;
try {
points = SplinePointsFromSplines(
SplineHelper::QuinticSplinesFromWaypoints(newWaypoints));
points = SplinePointsFromSplines(SplineHelper::OptimizeCurvature(
SplineHelper::QuinticSplinesFromWaypoints(newWaypoints)));
} catch (SplineParameterizer::MalformedSplineException& e) {
ReportError(e.what());
return kDoNothingTrajectory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,36 @@ class WPILIB_DLLEXPORT CubicHermiteSpline : public Spline<3> {
wpi::array<double, 2> yInitialControlVector,
wpi::array<double, 2> yFinalControlVector);

protected:
/**
* Returns the coefficients matrix.
* @return The coefficients matrix.
*/
Matrixd<6, 3 + 1> Coefficients() const override { return m_coefficients; }

/**
* Returns the initial control vector that created this spline.
*
* @return The initial control vector that created this spline.
*/
const ControlVector& GetInitialControlVector() const override {
return m_initialControlVector;
}

/**
* Returns the final control vector that created this spline.
*
* @return The final control vector that created this spline.
*/
const ControlVector& GetFinalControlVector() const override {
return m_finalControlVector;
}

private:
Matrixd<6, 4> m_coefficients = Matrixd<6, 4>::Zero();

ControlVector m_initialControlVector;
ControlVector m_finalControlVector;

/**
* Returns the hermite basis matrix for cubic hermite spline interpolation.
* @return The hermite basis matrix for cubic hermite spline interpolation.
Expand Down
Loading