2022-04-24 07:21:40 -07:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
|
|
|
|
|
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
2025-11-07 19:56:21 -05:00
|
|
|
#include "wpi/math/controller/DifferentialDriveAccelerationLimiter.hpp"
|
|
|
|
|
#include "wpi/math/system/plant/LinearSystemId.hpp"
|
|
|
|
|
#include "wpi/units/math.hpp"
|
2022-04-24 07:21:40 -07:00
|
|
|
|
2025-11-07 20:00:05 -05:00
|
|
|
namespace wpi::math {
|
2022-04-24 07:21:40 -07:00
|
|
|
|
|
|
|
|
TEST(DifferentialDriveAccelerationLimiterTest, LowLimits) {
|
|
|
|
|
constexpr auto trackwidth = 0.9_m;
|
2025-11-07 20:00:05 -05:00
|
|
|
constexpr wpi::units::second_t dt = 5_ms;
|
2022-04-24 07:21:40 -07:00
|
|
|
constexpr auto maxA = 2_mps_sq;
|
|
|
|
|
constexpr auto maxAlpha = 2_rad_per_s_sq;
|
|
|
|
|
|
|
|
|
|
using Kv_t = decltype(1_V / 1_mps);
|
|
|
|
|
using Ka_t = decltype(1_V / 1_mps_sq);
|
|
|
|
|
auto plant = LinearSystemId::IdentifyDrivetrainSystem(Kv_t{1.0}, Ka_t{1.0},
|
|
|
|
|
Kv_t{1.0}, Ka_t{1.0});
|
|
|
|
|
|
|
|
|
|
DifferentialDriveAccelerationLimiter accelLimiter{plant, trackwidth, maxA,
|
|
|
|
|
maxAlpha};
|
|
|
|
|
|
2022-04-29 22:29:20 -07:00
|
|
|
Vectord<2> x{0.0, 0.0};
|
|
|
|
|
Vectord<2> xAccelLimiter{0.0, 0.0};
|
2022-04-24 07:21:40 -07:00
|
|
|
|
|
|
|
|
// Ensure voltage exceeds acceleration before limiting
|
|
|
|
|
{
|
2022-04-29 22:29:20 -07:00
|
|
|
Vectord<2> accels =
|
|
|
|
|
plant.A() * xAccelLimiter + plant.B() * Vectord<2>{12.0, 12.0};
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::units::meters_per_second_squared_t a{(accels(0) + accels(1)) / 2.0};
|
|
|
|
|
EXPECT_GT(wpi::units::math::abs(a), maxA);
|
2022-04-24 07:21:40 -07:00
|
|
|
}
|
|
|
|
|
{
|
2022-04-29 22:29:20 -07:00
|
|
|
Vectord<2> accels =
|
|
|
|
|
plant.A() * xAccelLimiter + plant.B() * Vectord<2>{-12.0, 12.0};
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::units::radians_per_second_squared_t alpha{(accels(1) - accels(0)) /
|
2025-11-07 20:01:58 -05:00
|
|
|
trackwidth.value()};
|
2025-11-07 20:00:05 -05:00
|
|
|
EXPECT_GT(wpi::units::math::abs(alpha), maxAlpha);
|
2022-04-24 07:21:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Forward
|
2022-04-29 22:29:20 -07:00
|
|
|
Vectord<2> u{12.0, 12.0};
|
2022-04-24 07:21:40 -07:00
|
|
|
for (auto t = 0_s; t < 3_s; t += dt) {
|
|
|
|
|
x = plant.CalculateX(x, u, dt);
|
2025-11-07 20:01:58 -05:00
|
|
|
auto [left, right] = accelLimiter.Calculate(
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(0)},
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(1)},
|
|
|
|
|
wpi::units::volt_t{u(0)}, wpi::units::volt_t{u(1)});
|
2022-04-29 22:29:20 -07:00
|
|
|
xAccelLimiter =
|
|
|
|
|
plant.CalculateX(xAccelLimiter, Vectord<2>{left, right}, dt);
|
2022-04-24 07:21:40 -07:00
|
|
|
|
2022-04-29 22:29:20 -07:00
|
|
|
Vectord<2> accels =
|
|
|
|
|
plant.A() * xAccelLimiter + plant.B() * Vectord<2>{left, right};
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::units::meters_per_second_squared_t a{(accels(0) + accels(1)) / 2.0};
|
|
|
|
|
wpi::units::radians_per_second_squared_t alpha{(accels(1) - accels(0)) /
|
2025-11-07 20:01:58 -05:00
|
|
|
trackwidth.value()};
|
2025-11-07 20:00:05 -05:00
|
|
|
EXPECT_LE(wpi::units::math::abs(a), maxA);
|
|
|
|
|
EXPECT_LE(wpi::units::math::abs(alpha), maxAlpha);
|
2022-04-24 07:21:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Backward
|
2022-04-29 22:29:20 -07:00
|
|
|
u = Vectord<2>{-12.0, -12.0};
|
2022-04-24 07:21:40 -07:00
|
|
|
for (auto t = 0_s; t < 3_s; t += dt) {
|
|
|
|
|
x = plant.CalculateX(x, u, dt);
|
2025-11-07 20:01:58 -05:00
|
|
|
auto [left, right] = accelLimiter.Calculate(
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(0)},
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(1)},
|
|
|
|
|
wpi::units::volt_t{u(0)}, wpi::units::volt_t{u(1)});
|
2022-04-29 22:29:20 -07:00
|
|
|
xAccelLimiter =
|
|
|
|
|
plant.CalculateX(xAccelLimiter, Vectord<2>{left, right}, dt);
|
2022-04-24 07:21:40 -07:00
|
|
|
|
2022-04-29 22:29:20 -07:00
|
|
|
Vectord<2> accels =
|
|
|
|
|
plant.A() * xAccelLimiter + plant.B() * Vectord<2>{left, right};
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::units::meters_per_second_squared_t a{(accels(0) + accels(1)) / 2.0};
|
|
|
|
|
wpi::units::radians_per_second_squared_t alpha{(accels(1) - accels(0)) /
|
2025-11-07 20:01:58 -05:00
|
|
|
trackwidth.value()};
|
2025-11-07 20:00:05 -05:00
|
|
|
EXPECT_LE(wpi::units::math::abs(a), maxA);
|
|
|
|
|
EXPECT_LE(wpi::units::math::abs(alpha), maxAlpha);
|
2022-04-24 07:21:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Rotate CCW
|
2022-04-29 22:29:20 -07:00
|
|
|
u = Vectord<2>{-12.0, 12.0};
|
2022-04-24 07:21:40 -07:00
|
|
|
for (auto t = 0_s; t < 3_s; t += dt) {
|
|
|
|
|
x = plant.CalculateX(x, u, dt);
|
2025-11-07 20:01:58 -05:00
|
|
|
auto [left, right] = accelLimiter.Calculate(
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(0)},
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(1)},
|
|
|
|
|
wpi::units::volt_t{u(0)}, wpi::units::volt_t{u(1)});
|
2022-04-29 22:29:20 -07:00
|
|
|
xAccelLimiter =
|
|
|
|
|
plant.CalculateX(xAccelLimiter, Vectord<2>{left, right}, dt);
|
2022-04-24 07:21:40 -07:00
|
|
|
|
2022-04-29 22:29:20 -07:00
|
|
|
Vectord<2> accels =
|
|
|
|
|
plant.A() * xAccelLimiter + plant.B() * Vectord<2>{left, right};
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::units::meters_per_second_squared_t a{(accels(0) + accels(1)) / 2.0};
|
|
|
|
|
wpi::units::radians_per_second_squared_t alpha{(accels(1) - accels(0)) /
|
2025-11-07 20:01:58 -05:00
|
|
|
trackwidth.value()};
|
2025-11-07 20:00:05 -05:00
|
|
|
EXPECT_LE(wpi::units::math::abs(a), maxA);
|
|
|
|
|
EXPECT_LE(wpi::units::math::abs(alpha), maxAlpha);
|
2022-04-24 07:21:40 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DifferentialDriveAccelerationLimiterTest, HighLimits) {
|
|
|
|
|
constexpr auto trackwidth = 0.9_m;
|
2025-11-07 20:00:05 -05:00
|
|
|
constexpr wpi::units::second_t dt = 5_ms;
|
2022-04-24 07:21:40 -07:00
|
|
|
|
|
|
|
|
using Kv_t = decltype(1_V / 1_mps);
|
|
|
|
|
using Ka_t = decltype(1_V / 1_mps_sq);
|
|
|
|
|
|
|
|
|
|
auto plant = LinearSystemId::IdentifyDrivetrainSystem(Kv_t{1.0}, Ka_t{1.0},
|
|
|
|
|
Kv_t{1.0}, Ka_t{1.0});
|
|
|
|
|
|
|
|
|
|
// Limits are so high, they don't get hit, so states of constrained and
|
|
|
|
|
// unconstrained systems should match
|
|
|
|
|
DifferentialDriveAccelerationLimiter accelLimiter{
|
|
|
|
|
plant, trackwidth, 1e3_mps_sq, 1e3_rad_per_s_sq};
|
|
|
|
|
|
2022-04-29 22:29:20 -07:00
|
|
|
Vectord<2> x{0.0, 0.0};
|
|
|
|
|
Vectord<2> xAccelLimiter{0.0, 0.0};
|
2022-04-24 07:21:40 -07:00
|
|
|
|
|
|
|
|
// Forward
|
2022-04-29 22:29:20 -07:00
|
|
|
Vectord<2> u{12.0, 12.0};
|
2022-04-24 07:21:40 -07:00
|
|
|
for (auto t = 0_s; t < 3_s; t += dt) {
|
|
|
|
|
x = plant.CalculateX(x, u, dt);
|
2025-11-07 20:01:58 -05:00
|
|
|
auto [left, right] = accelLimiter.Calculate(
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(0)},
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(1)},
|
|
|
|
|
wpi::units::volt_t{u(0)}, wpi::units::volt_t{u(1)});
|
2022-04-29 22:29:20 -07:00
|
|
|
xAccelLimiter =
|
|
|
|
|
plant.CalculateX(xAccelLimiter, Vectord<2>{left, right}, dt);
|
2022-04-24 07:21:40 -07:00
|
|
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(x(0), xAccelLimiter(0));
|
|
|
|
|
EXPECT_DOUBLE_EQ(x(1), xAccelLimiter(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Backward
|
|
|
|
|
x.setZero();
|
|
|
|
|
xAccelLimiter.setZero();
|
2022-04-29 22:29:20 -07:00
|
|
|
u = Vectord<2>{-12.0, -12.0};
|
2022-04-24 07:21:40 -07:00
|
|
|
for (auto t = 0_s; t < 3_s; t += dt) {
|
|
|
|
|
x = plant.CalculateX(x, u, dt);
|
2025-11-07 20:01:58 -05:00
|
|
|
auto [left, right] = accelLimiter.Calculate(
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(0)},
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(1)},
|
|
|
|
|
wpi::units::volt_t{u(0)}, wpi::units::volt_t{u(1)});
|
2022-04-29 22:29:20 -07:00
|
|
|
xAccelLimiter =
|
|
|
|
|
plant.CalculateX(xAccelLimiter, Vectord<2>{left, right}, dt);
|
2022-04-24 07:21:40 -07:00
|
|
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(x(0), xAccelLimiter(0));
|
|
|
|
|
EXPECT_DOUBLE_EQ(x(1), xAccelLimiter(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Rotate CCW
|
|
|
|
|
x.setZero();
|
|
|
|
|
xAccelLimiter.setZero();
|
2022-04-29 22:29:20 -07:00
|
|
|
u = Vectord<2>{-12.0, 12.0};
|
2022-04-24 07:21:40 -07:00
|
|
|
for (auto t = 0_s; t < 3_s; t += dt) {
|
|
|
|
|
x = plant.CalculateX(x, u, dt);
|
2025-11-07 20:01:58 -05:00
|
|
|
auto [left, right] = accelLimiter.Calculate(
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(0)},
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(1)},
|
|
|
|
|
wpi::units::volt_t{u(0)}, wpi::units::volt_t{u(1)});
|
2022-04-29 22:29:20 -07:00
|
|
|
xAccelLimiter =
|
|
|
|
|
plant.CalculateX(xAccelLimiter, Vectord<2>{left, right}, dt);
|
2022-04-24 07:21:40 -07:00
|
|
|
|
|
|
|
|
EXPECT_DOUBLE_EQ(x(0), xAccelLimiter(0));
|
|
|
|
|
EXPECT_DOUBLE_EQ(x(1), xAccelLimiter(1));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-17 10:44:34 -04:00
|
|
|
TEST(DifferentialDriveAccelerationLimiterTest, SeparateMinMaxLowLimits) {
|
2022-10-10 11:57:37 -04:00
|
|
|
constexpr auto trackwidth = 0.9_m;
|
2025-11-07 20:00:05 -05:00
|
|
|
constexpr wpi::units::second_t dt = 5_ms;
|
2022-10-10 11:57:37 -04:00
|
|
|
constexpr auto minA = -1_mps_sq;
|
|
|
|
|
constexpr auto maxA = 2_mps_sq;
|
|
|
|
|
constexpr auto maxAlpha = 2_rad_per_s_sq;
|
|
|
|
|
|
|
|
|
|
using Kv_t = decltype(1_V / 1_mps);
|
|
|
|
|
using Ka_t = decltype(1_V / 1_mps_sq);
|
|
|
|
|
auto plant = LinearSystemId::IdentifyDrivetrainSystem(Kv_t{1.0}, Ka_t{1.0},
|
|
|
|
|
Kv_t{1.0}, Ka_t{1.0});
|
|
|
|
|
|
|
|
|
|
DifferentialDriveAccelerationLimiter accelLimiter{plant, trackwidth, minA,
|
|
|
|
|
maxA, maxAlpha};
|
|
|
|
|
|
|
|
|
|
Vectord<2> x{0.0, 0.0};
|
|
|
|
|
Vectord<2> xAccelLimiter{0.0, 0.0};
|
|
|
|
|
|
|
|
|
|
// Ensure voltage exceeds acceleration before limiting
|
|
|
|
|
{
|
|
|
|
|
Vectord<2> accels =
|
|
|
|
|
plant.A() * xAccelLimiter + plant.B() * Vectord<2>{12.0, 12.0};
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::units::meters_per_second_squared_t a{(accels(0) + accels(1)) / 2.0};
|
|
|
|
|
EXPECT_GT(wpi::units::math::abs(a), maxA);
|
|
|
|
|
EXPECT_GT(wpi::units::math::abs(a), -minA);
|
2022-10-10 11:57:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// a should always be within [minA, maxA]
|
|
|
|
|
// Forward
|
|
|
|
|
Vectord<2> u{12.0, 12.0};
|
|
|
|
|
for (auto t = 0_s; t < 3_s; t += dt) {
|
|
|
|
|
x = plant.CalculateX(x, u, dt);
|
2025-11-07 20:01:58 -05:00
|
|
|
auto [left, right] = accelLimiter.Calculate(
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(0)},
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(1)},
|
|
|
|
|
wpi::units::volt_t{u(0)}, wpi::units::volt_t{u(1)});
|
2022-10-10 11:57:37 -04:00
|
|
|
xAccelLimiter =
|
|
|
|
|
plant.CalculateX(xAccelLimiter, Vectord<2>{left, right}, dt);
|
|
|
|
|
|
|
|
|
|
Vectord<2> accels =
|
|
|
|
|
plant.A() * xAccelLimiter + plant.B() * Vectord<2>{left, right};
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::units::meters_per_second_squared_t a{(accels(0) + accels(1)) / 2.0};
|
2022-10-10 11:57:37 -04:00
|
|
|
EXPECT_GE(a, minA);
|
|
|
|
|
EXPECT_LE(a, maxA);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Backward
|
|
|
|
|
u = Vectord<2>{-12.0, -12.0};
|
|
|
|
|
for (auto t = 0_s; t < 3_s; t += dt) {
|
|
|
|
|
x = plant.CalculateX(x, u, dt);
|
2025-11-07 20:01:58 -05:00
|
|
|
auto [left, right] = accelLimiter.Calculate(
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(0)},
|
|
|
|
|
wpi::units::meters_per_second_t{xAccelLimiter(1)},
|
|
|
|
|
wpi::units::volt_t{u(0)}, wpi::units::volt_t{u(1)});
|
2022-10-10 11:57:37 -04:00
|
|
|
xAccelLimiter =
|
|
|
|
|
plant.CalculateX(xAccelLimiter, Vectord<2>{left, right}, dt);
|
|
|
|
|
|
|
|
|
|
Vectord<2> accels =
|
|
|
|
|
plant.A() * xAccelLimiter + plant.B() * Vectord<2>{left, right};
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::units::meters_per_second_squared_t a{(accels(0) + accels(1)) / 2.0};
|
2022-10-10 11:57:37 -04:00
|
|
|
EXPECT_GE(a, minA);
|
|
|
|
|
EXPECT_LE(a, maxA);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DifferentialDriveAccelerationLimiterTest, MinAccelGreaterThanMaxAccel) {
|
|
|
|
|
using Kv_t = decltype(1_V / 1_mps);
|
|
|
|
|
using Ka_t = decltype(1_V / 1_mps_sq);
|
|
|
|
|
auto plant = LinearSystemId::IdentifyDrivetrainSystem(Kv_t{1.0}, Ka_t{1.0},
|
|
|
|
|
Kv_t{1.0}, Ka_t{1.0});
|
|
|
|
|
EXPECT_NO_THROW({
|
|
|
|
|
DifferentialDriveAccelerationLimiter accelLimiter(plant, 1_m, 1_mps_sq,
|
|
|
|
|
1_rad_per_s_sq);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
EXPECT_THROW(
|
|
|
|
|
{
|
|
|
|
|
DifferentialDriveAccelerationLimiter accelLimiter(
|
|
|
|
|
plant, 1_m, 1_mps_sq, -1_mps_sq, 1_rad_per_s_sq);
|
|
|
|
|
},
|
|
|
|
|
std::invalid_argument);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-07 20:00:05 -05:00
|
|
|
} // namespace wpi::math
|