diff --git a/wpimath/src/main/java/edu/wpi/first/math/system/LinearSystem.java b/wpimath/src/main/java/edu/wpi/first/math/system/LinearSystem.java index b7b191a9bd..9e35cfdeaf 100644 --- a/wpimath/src/main/java/edu/wpi/first/math/system/LinearSystem.java +++ b/wpimath/src/main/java/edu/wpi/first/math/system/LinearSystem.java @@ -33,6 +33,7 @@ public class LinearSystem b, Matrix c, Matrix d) { + for (int row = 0; row < a.getNumRows(); ++row) { + for (int col = 0; col < a.getNumCols(); ++col) { + if (!Double.isFinite(a.get(row, col))) { + throw new IllegalArgumentException( + "Elements of A aren't finite. This is usually due to model implementation errors."); + } + } + } + for (int row = 0; row < b.getNumRows(); ++row) { + for (int col = 0; col < b.getNumCols(); ++col) { + if (!Double.isFinite(b.get(row, col))) { + throw new IllegalArgumentException( + "Elements of B aren't finite. This is usually due to model implementation errors."); + } + } + } + for (int row = 0; row < c.getNumRows(); ++row) { + for (int col = 0; col < c.getNumCols(); ++col) { + if (!Double.isFinite(c.get(row, col))) { + throw new IllegalArgumentException( + "Elements of C aren't finite. This is usually due to model implementation errors."); + } + } + } + for (int row = 0; row < d.getNumRows(); ++row) { + for (int col = 0; col < d.getNumCols(); ++col) { + if (!Double.isFinite(d.get(row, col))) { + throw new IllegalArgumentException( + "Elements of D aren't finite. This is usually due to model implementation errors."); + } + } + } + this.m_A = a; this.m_B = b; this.m_C = c; diff --git a/wpimath/src/main/java/edu/wpi/first/math/system/plant/LinearSystemId.java b/wpimath/src/main/java/edu/wpi/first/math/system/plant/LinearSystemId.java index e5b9bde567..2933e937f7 100644 --- a/wpimath/src/main/java/edu/wpi/first/math/system/plant/LinearSystemId.java +++ b/wpimath/src/main/java/edu/wpi/first/math/system/plant/LinearSystemId.java @@ -25,10 +25,21 @@ public final class LinearSystemId { * @param radiusMeters The radius of thd driving drum of the elevator, in meters. * @param G The reduction between motor and drum, as a ratio of output to input. * @return A LinearSystem representing the given characterized constants. + * @throws IllegalArgumentException if massKg <= 0, radiusMeters <= 0, or G <= 0. */ @SuppressWarnings("ParameterName") public static LinearSystem createElevatorSystem( DCMotor motor, double massKg, double radiusMeters, double G) { + if (massKg <= 0.0) { + throw new IllegalArgumentException("massKg must be greater than zero."); + } + if (radiusMeters <= 0.0) { + throw new IllegalArgumentException("radiusMeters must be greater than zero."); + } + if (G <= 0) { + throw new IllegalArgumentException("G must be greater than zero."); + } + return new LinearSystem<>( Matrix.mat(Nat.N2(), Nat.N2()) .fill( @@ -55,10 +66,18 @@ public final class LinearSystemId { * @param jKgMetersSquared The moment of inertia J of the flywheel. * @param G The reduction between motor and drum, as a ratio of output to input. * @return A LinearSystem representing the given characterized constants. + * @throws IllegalArgumentException if jKgMetersSquared <= 0 or G <= 0. */ @SuppressWarnings("ParameterName") public static LinearSystem createFlywheelSystem( DCMotor motor, double jKgMetersSquared, double G) { + if (jKgMetersSquared <= 0.0) { + throw new IllegalArgumentException("J must be greater than zero."); + } + if (G <= 0.0) { + throw new IllegalArgumentException("G must be greater than zero."); + } + return new LinearSystem<>( VecBuilder.fill( -G @@ -81,6 +100,7 @@ public final class LinearSystemId { * @param JKgMetersSquared the moment of inertia of the robot. * @param G the gearing reduction as output over input. * @return A LinearSystem representing a differential drivetrain. + * @throws IllegalArgumentException if m <= 0, r <= 0, rb <= 0, J <= 0, or G <= 0. */ @SuppressWarnings({"LocalVariableName", "ParameterName"}) public static LinearSystem createDrivetrainVelocitySystem( @@ -90,6 +110,22 @@ public final class LinearSystemId { double rbMeters, double JKgMetersSquared, double G) { + if (massKg <= 0.0) { + throw new IllegalArgumentException("massKg must be greater than zero."); + } + if (rMeters <= 0.0) { + throw new IllegalArgumentException("rMeters must be greater than zero."); + } + if (rbMeters <= 0.0) { + throw new IllegalArgumentException("rbMeters must be greater than zero."); + } + if (JKgMetersSquared <= 0.0) { + throw new IllegalArgumentException("JKgMetersSquared must be greater than zero."); + } + if (G <= 0.0) { + throw new IllegalArgumentException("G must be greater than zero."); + } + var C1 = -(G * G) * motor.KtNMPerAmp / (motor.KvRadPerSecPerVolt * motor.rOhms * rMeters * rMeters); var C2 = G * motor.KtNMPerAmp / (motor.rOhms * rMeters); @@ -117,6 +153,13 @@ public final class LinearSystemId { @SuppressWarnings("ParameterName") public static LinearSystem createSingleJointedArmSystem( DCMotor motor, double jKgSquaredMeters, double G) { + if (jKgSquaredMeters <= 0.0) { + throw new IllegalArgumentException("jKgSquaredMeters must be greater than zero."); + } + if (G <= 0.0) { + throw new IllegalArgumentException("G must be greater than zero."); + } + return new LinearSystem<>( Matrix.mat(Nat.N2(), Nat.N2()) .fill( @@ -142,10 +185,18 @@ public final class LinearSystemId { * @param kV The velocity gain, in volts per (units per second) * @param kA The acceleration gain, in volts per (units per second squared) * @return A LinearSystem representing the given characterized constants. + * @throws IllegalArgumentException if kV <= 0 or kA <= 0. * @see https://github.com/wpilibsuite/sysid */ @SuppressWarnings("ParameterName") public static LinearSystem identifyVelocitySystem(double kV, double kA) { + if (kV <= 0.0) { + throw new IllegalArgumentException("Kv must be greater than zero."); + } + if (kA <= 0.0) { + throw new IllegalArgumentException("Ka must be greater than zero."); + } + return new LinearSystem<>( VecBuilder.fill(-kV / kA), VecBuilder.fill(1.0 / kA), @@ -164,10 +215,18 @@ public final class LinearSystemId { * @param kV The velocity gain, in volts per (units per second) * @param kA The acceleration gain, in volts per (units per second squared) * @return A LinearSystem representing the given characterized constants. + * @throws IllegalArgumentException if kV <= 0 or kA <= 0. * @see https://github.com/wpilibsuite/sysid */ @SuppressWarnings("ParameterName") public static LinearSystem identifyPositionSystem(double kV, double kA) { + if (kV <= 0.0) { + throw new IllegalArgumentException("Kv must be greater than zero."); + } + if (kA <= 0.0) { + throw new IllegalArgumentException("Ka must be greater than zero."); + } + return new LinearSystem<>( Matrix.mat(Nat.N2(), Nat.N2()).fill(0.0, 1.0, 0.0, -kV / kA), VecBuilder.fill(0.0, 1.0 / kA), @@ -187,11 +246,26 @@ public final class LinearSystemId { * @param kVAngular The angular velocity gain, volts per (meter per second). * @param kAAngular The angular acceleration gain, volts per (meter per second squared). * @return A LinearSystem representing the given characterized constants. + * @throws IllegalArgumentException if kVLinear <= 0, kALinear <= 0, kVAngular <= 0, or + * kAAngular <= 0. * @see https://github.com/wpilibsuite/sysid */ @SuppressWarnings("ParameterName") public static LinearSystem identifyDrivetrainSystem( double kVLinear, double kALinear, double kVAngular, double kAAngular) { + if (kVLinear <= 0.0) { + throw new IllegalArgumentException("Kv,linear must be greater than zero."); + } + if (kALinear <= 0.0) { + throw new IllegalArgumentException("Ka,linear must be greater than zero."); + } + if (kVAngular <= 0.0) { + throw new IllegalArgumentException("Kv,angular must be greater than zero."); + } + if (kAAngular <= 0.0) { + throw new IllegalArgumentException("Ka,angular must be greater than zero."); + } + final double A1 = 0.5 * -(kVLinear / kALinear + kVAngular / kAAngular); final double A2 = 0.5 * -(kVLinear / kALinear - kVAngular / kAAngular); final double B1 = 0.5 * (1.0 / kALinear + 1.0 / kAAngular); @@ -217,11 +291,29 @@ public final class LinearSystemId { * @param kAAngular The angular acceleration gain, volts per (radians per second squared). * @param trackwidth The width of the drivetrain in meters. * @return A LinearSystem representing the given characterized constants. + * @throws IllegalArgumentException if kVLinear <= 0, kALinear <= 0, kVAngular <= 0, + * kAAngular <= 0, or trackwidth <= 0. * @see https://github.com/wpilibsuite/sysid */ @SuppressWarnings("ParameterName") public static LinearSystem identifyDrivetrainSystem( double kVLinear, double kALinear, double kVAngular, double kAAngular, double trackwidth) { + if (kVLinear <= 0.0) { + throw new IllegalArgumentException("Kv,linear must be greater than zero."); + } + if (kALinear <= 0.0) { + throw new IllegalArgumentException("Ka,linear must be greater than zero."); + } + if (kVAngular <= 0.0) { + throw new IllegalArgumentException("Kv,angular must be greater than zero."); + } + if (kAAngular <= 0.0) { + throw new IllegalArgumentException("Ka,angular must be greater than zero."); + } + if (trackwidth <= 0.0) { + throw new IllegalArgumentException("trackwidth must be greater than zero."); + } + // We want to find a factor to include in Kv,angular that will convert // `u = Kv,angular omega` to `u = Kv,angular v`. // diff --git a/wpimath/src/main/native/include/frc/system/LinearSystem.h b/wpimath/src/main/native/include/frc/system/LinearSystem.h index 5f12e3a663..dac3c60599 100644 --- a/wpimath/src/main/native/include/frc/system/LinearSystem.h +++ b/wpimath/src/main/native/include/frc/system/LinearSystem.h @@ -6,6 +6,7 @@ #include #include +#include #include "Eigen/Core" #include "frc/StateSpaceUtil.h" @@ -32,11 +33,33 @@ class LinearSystem { * @param B Input matrix. * @param C Output matrix. * @param D Feedthrough matrix. + * @throws std::domain_error if any matrix element isn't finite. */ LinearSystem(const Eigen::Matrix& A, const Eigen::Matrix& B, const Eigen::Matrix& C, const Eigen::Matrix& D) { + if (!A.allFinite()) { + throw std::domain_error( + "Elements of A aren't finite. This is usually due to model " + "implementation errors."); + } + if (!B.allFinite()) { + throw std::domain_error( + "Elements of B aren't finite. This is usually due to model " + "implementation errors."); + } + if (!C.allFinite()) { + throw std::domain_error( + "Elements of C aren't finite. This is usually due to model " + "implementation errors."); + } + if (!D.allFinite()) { + throw std::domain_error( + "Elements of D aren't finite. This is usually due to model " + "implementation errors."); + } + m_A = A; m_B = B; m_C = C; diff --git a/wpimath/src/main/native/include/frc/system/plant/LinearSystemId.h b/wpimath/src/main/native/include/frc/system/plant/LinearSystemId.h index d9e6e9bfe4..c0f4506e0d 100644 --- a/wpimath/src/main/native/include/frc/system/plant/LinearSystemId.h +++ b/wpimath/src/main/native/include/frc/system/plant/LinearSystemId.h @@ -4,6 +4,8 @@ #pragma once +#include + #include #include "frc/system/LinearSystem.h" @@ -39,10 +41,21 @@ class WPILIB_DLLEXPORT LinearSystemId { * @param m Carriage mass. * @param r Pulley radius. * @param G Gear ratio from motor to carriage. + * @throws std::domain_error if m <= 0, r <= 0, or G <= 0. */ static LinearSystem<2, 1, 1> ElevatorSystem(DCMotor motor, units::kilogram_t m, units::meter_t r, double G) { + if (m <= 0_kg) { + throw std::domain_error("m must be greater than zero."); + } + if (r <= 0_m) { + throw std::domain_error("r must be greater than zero."); + } + if (G <= 0.0) { + throw std::domain_error("G must be greater than zero."); + } + Eigen::Matrix A{ {0.0, 1.0}, {0.0, (-std::pow(G, 2) * motor.Kt / @@ -66,9 +79,17 @@ class WPILIB_DLLEXPORT LinearSystemId { * @param motor Instance of DCMotor. * @param J Moment of inertia. * @param G Gear ratio from motor to carriage. + * @throws std::domain_error if J <= 0 or G <= 0. */ static LinearSystem<2, 1, 1> SingleJointedArmSystem( DCMotor motor, units::kilogram_square_meter_t J, double G) { + if (J <= 0_kg_sq_m) { + throw std::domain_error("J must be greater than zero."); + } + if (G <= 0.0) { + throw std::domain_error("G must be greater than zero."); + } + Eigen::Matrix A{ {0.0, 1.0}, {0.0, (-std::pow(G, 2) * motor.Kt / (motor.Kv * motor.R * J)).value()}}; @@ -98,6 +119,7 @@ class WPILIB_DLLEXPORT LinearSystemId { * * @param kV The velocity gain, in volt seconds per distance. * @param kA The acceleration gain, in volt seconds^2 per distance. + * @throws std::domain_error if kV <= 0 or kA <= 0. */ template || @@ -105,6 +127,13 @@ class WPILIB_DLLEXPORT LinearSystemId { static LinearSystem<1, 1, 1> IdentifyVelocitySystem( decltype(1_V / Velocity_t(1)) kV, decltype(1_V / Acceleration_t(1)) kA) { + if (kV <= decltype(kV){0}) { + throw std::domain_error("Kv must be greater than zero."); + } + if (kA <= decltype(kA){0}) { + throw std::domain_error("Ka must be greater than zero."); + } + Eigen::Matrix A{-kV.value() / kA.value()}; Eigen::Matrix B{1.0 / kA.value()}; Eigen::Matrix C{1.0}; @@ -132,6 +161,7 @@ class WPILIB_DLLEXPORT LinearSystemId { * * @param kV The velocity gain, in volt seconds per distance. * @param kA The acceleration gain, in volt seconds^2 per distance. + * @throws std::domain_error if kV <= 0 or kA <= 0. */ template || @@ -139,6 +169,13 @@ class WPILIB_DLLEXPORT LinearSystemId { static LinearSystem<2, 1, 1> IdentifyPositionSystem( decltype(1_V / Velocity_t(1)) kV, decltype(1_V / Acceleration_t(1)) kA) { + if (kV <= decltype(kV){0}) { + throw std::domain_error("Kv must be greater than zero."); + } + if (kA <= decltype(kA){0}) { + throw std::domain_error("Ka must be greater than zero."); + } + Eigen::Matrix A{{0.0, 1.0}, {0.0, -kV.value() / kA.value()}}; Eigen::Matrix B{0.0, 1.0 / kA.value()}; Eigen::Matrix C{1.0, 0.0}; @@ -161,10 +198,25 @@ class WPILIB_DLLEXPORT LinearSystemId { * @param kVangular The angular velocity gain in volts per (meter per second). * @param kAangular The angular acceleration gain in volts per (meter per * second squared). + * @throws domain_error if kVlinear <= 0, kAlinear <= 0, kVangular <= 0, + * or kAangular <= 0. */ static LinearSystem<2, 2, 2> IdentifyDrivetrainSystem( decltype(1_V / 1_mps) kVlinear, decltype(1_V / 1_mps_sq) kAlinear, decltype(1_V / 1_mps) kVangular, decltype(1_V / 1_mps_sq) kAangular) { + if (kVlinear <= decltype(kVlinear){0}) { + throw std::domain_error("Kv,linear must be greater than zero."); + } + if (kAlinear <= decltype(kAlinear){0}) { + throw std::domain_error("Ka,linear must be greater than zero."); + } + if (kVangular <= decltype(kVangular){0}) { + throw std::domain_error("Kv,angular must be greater than zero."); + } + if (kAangular <= decltype(kAangular){0}) { + throw std::domain_error("Ka,angular must be greater than zero."); + } + double A1 = -(kVlinear.value() / kAlinear.value() + kVangular.value() / kAangular.value()); double A2 = -(kVlinear.value() / kAlinear.value() - @@ -198,11 +250,29 @@ class WPILIB_DLLEXPORT LinearSystemId { * @param kAangular The angular acceleration gain in volts per (radian per * second squared). * @param trackwidth The width of the drivetrain. + * @throws domain_error if kVlinear <= 0, kAlinear <= 0, kVangular <= 0, + * kAangular <= 0, or trackwidth <= 0. */ static LinearSystem<2, 2, 2> IdentifyDrivetrainSystem( decltype(1_V / 1_mps) kVlinear, decltype(1_V / 1_mps_sq) kAlinear, decltype(1_V / 1_rad_per_s) kVangular, decltype(1_V / 1_rad_per_s_sq) kAangular, units::meter_t trackwidth) { + if (kVlinear <= decltype(kVlinear){0}) { + throw std::domain_error("Kv,linear must be greater than zero."); + } + if (kAlinear <= decltype(kAlinear){0}) { + throw std::domain_error("Ka,linear must be greater than zero."); + } + if (kVangular <= decltype(kVangular){0}) { + throw std::domain_error("Kv,angular must be greater than zero."); + } + if (kAangular <= decltype(kAangular){0}) { + throw std::domain_error("Ka,angular must be greater than zero."); + } + if (trackwidth <= 0_m) { + throw std::domain_error("r must be greater than zero."); + } + // We want to find a factor to include in Kv,angular that will convert // `u = Kv,angular omega` to `u = Kv,angular v`. // @@ -229,10 +299,18 @@ class WPILIB_DLLEXPORT LinearSystemId { * @param motor Instance of DCMotor. * @param J Moment of inertia. * @param G Gear ratio from motor to carriage. + * @throws std::domain_error if J <= 0 or G <= 0. */ static LinearSystem<1, 1, 1> FlywheelSystem(DCMotor motor, units::kilogram_square_meter_t J, double G) { + if (J <= 0_kg_sq_m) { + throw std::domain_error("J must be greater than zero."); + } + if (G <= 0.0) { + throw std::domain_error("G must be greater than zero."); + } + Eigen::Matrix A{ (-std::pow(G, 2) * motor.Kt / (motor.Kv * motor.R * J)).value()}; Eigen::Matrix B{(G * motor.Kt / (motor.R * J)).value()}; @@ -253,12 +331,30 @@ class WPILIB_DLLEXPORT LinearSystemId { * @param m Drivetrain mass. * @param r Wheel radius. * @param rb Robot radius. - * @param G Gear ratio from motor to wheel. * @param J Moment of inertia. + * @param G Gear ratio from motor to wheel. + * @throws std::domain_error if m <= 0, r <= 0, rb <= 0, J <= 0, or + * G <= 0. */ static LinearSystem<2, 2, 2> DrivetrainVelocitySystem( const DCMotor& motor, units::kilogram_t m, units::meter_t r, units::meter_t rb, units::kilogram_square_meter_t J, double G) { + if (m <= 0_kg) { + throw std::domain_error("m must be greater than zero."); + } + if (r <= 0_m) { + throw std::domain_error("r must be greater than zero."); + } + if (rb <= 0_m) { + throw std::domain_error("rb must be greater than zero."); + } + if (J <= 0_kg_sq_m) { + throw std::domain_error("J must be greater than zero."); + } + if (G <= 0.0) { + throw std::domain_error("G must be greater than zero."); + } + auto C1 = -std::pow(G, 2) * motor.Kt / (motor.Kv * motor.R * units::math::pow<2>(r)); auto C2 = G * motor.Kt / (motor.R * r);